mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 06:30:03 +00:00
feat(studio): one-line track header, controls right-aligned
The name, the clip count and every control now share one line, with the controls anchored to the right edge: eye, then FX, then the automation toggle. The two-line split this replaces existed to stop four controls truncating the name to a few characters. It does not need a second line to do that: the name already truncates on its own, and the controls are `shrink-0`, so they hold the edge and the name gives way instead. `ml-auto` on the control group absorbs whatever slack the name leaves, which is what keeps the buttons on the edge at any name length. The clip count sits against the name rather than out with the controls. That took dropping `flex-1` from the name — with it, the name claimed all the free width and pushed the badge across the row to meet the buttons. Now the badge tracks the name's own width: measured 4px after it on every row, whatever the name's length. Also folds away the class of bug the last two commits fixed: with one line and one right-aligned group there is no second line for a control to be misfiled onto, and nothing to centre in a box that grows. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b0bbbad95b
commit
4da0009bdd
@@ -891,7 +891,11 @@ describe("TimelineTrackHeader", () => {
|
|||||||
act(() => view.root.unmount());
|
act(() => view.root.unmount());
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps the group pointer on the control line, not a third row", () => {
|
// The header is ONE line: name, clip count, then every control anchored to
|
||||||
|
// the right edge. It was two — a name line and a control line — which is
|
||||||
|
// what let a stray third child overflow the 48px box; now there is a single
|
||||||
|
// row and the controls share one right-aligned group.
|
||||||
|
it("keeps the name and every control on one line, controls to the right", () => {
|
||||||
enabledCanaries.add("audio-fx-rack");
|
enabledCanaries.add("audio-fx-rack");
|
||||||
enabledCanaries.add("audio-groups");
|
enabledCanaries.add("audio-groups");
|
||||||
const view = renderHeader({
|
const view = renderHeader({
|
||||||
@@ -903,14 +907,20 @@ describe("TimelineTrackHeader", () => {
|
|||||||
isAudioTrack: true,
|
isAudioTrack: true,
|
||||||
});
|
});
|
||||||
const header = view.host.querySelector<HTMLElement>('[role="rowheader"]');
|
const header = view.host.querySelector<HTMLElement>('[role="rowheader"]');
|
||||||
// One TRACK_H-tall wrapper holding exactly the two lines.
|
// One TRACK_H-tall wrapper holding one line.
|
||||||
const lines = header?.children[0];
|
const lines = header?.children[0];
|
||||||
expect(lines?.children).toHaveLength(2);
|
expect(lines?.children).toHaveLength(1);
|
||||||
// And it is on the second line, beside the visibility control.
|
// The controls live in a right-anchored group at the end of that line,
|
||||||
const controlLine = lines?.children[1];
|
// after the name and the clip count — `ml-auto` is what holds the edge.
|
||||||
|
const line = lines?.children[0];
|
||||||
|
const controls = line?.lastElementChild as HTMLElement | null;
|
||||||
|
expect(controls?.className).toContain("ml-auto");
|
||||||
expect(
|
expect(
|
||||||
controlLine?.querySelector('button[aria-label="Effects — group these clips first"]'),
|
controls?.querySelector('button[aria-label="Effects — group these clips first"]'),
|
||||||
).not.toBeNull();
|
).not.toBeNull();
|
||||||
|
// And the clip count is beside the name, not out with the controls.
|
||||||
|
expect(controls?.querySelector('[aria-label="2 clips"]')).toBeNull();
|
||||||
|
expect(line?.querySelector('[aria-label="2 clips"]')).not.toBeNull();
|
||||||
act(() => view.root.unmount());
|
act(() => view.root.unmount());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -80,35 +80,43 @@ export function PlainTrackHeader({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Line one: what the row IS. Line two (below) is what you can do to it —
|
{/* One line: the name, then every control pushed to the right edge. The
|
||||||
the same split the group header uses, for the same reason: a name and
|
two-line split this replaced existed to stop four controls truncating
|
||||||
four controls sharing 232px truncated the name to a few characters. */}
|
the name — but the name already truncates on its own (`min-w-0` plus
|
||||||
|
`truncate`), and the controls are `shrink-0`, so they hold the edge
|
||||||
|
and the name gives way instead. */}
|
||||||
<div className="flex min-w-0 items-center gap-1">
|
<div className="flex min-w-0 items-center gap-1">
|
||||||
{isAudioTrack && (
|
{isAudioTrack && (
|
||||||
<Music size={12} weight="fill" aria-hidden="true" className="text-white/35" />
|
<Music size={12} weight="fill" aria-hidden="true" className="text-white/35" />
|
||||||
)}
|
)}
|
||||||
|
{/* No `flex-1`: the name takes only the width it needs (still
|
||||||
|
truncating at `min-w-0` when the row is narrow) so the clip count
|
||||||
|
sits against it rather than being pushed out to meet the controls.
|
||||||
|
The slack goes to the `ml-auto` group below instead. */}
|
||||||
{showTrackLabel && (
|
{showTrackLabel && (
|
||||||
<span className="min-w-0 flex-1 truncate text-[11px]" title={trackLabel}>
|
<span className="min-w-0 truncate text-[11px]" title={trackLabel}>
|
||||||
{trackLabel}
|
{trackLabel}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{showTrackLabel && <TrackClipCount clipCount={clipCount} />}
|
{showTrackLabel && <TrackClipCount clipCount={clipCount} />}
|
||||||
</div>
|
{/* `ml-auto` is what anchors the group right: it absorbs the slack the
|
||||||
<div className="flex items-center gap-1">
|
truncating name leaves, so the controls sit on the edge whatever the
|
||||||
{/* Not on an audio track. The control is the old visibility eye, and on
|
name's length. */}
|
||||||
audio it silences rather than hides — but a row that already says what
|
<div className="ml-auto flex shrink-0 items-center gap-1">
|
||||||
it is with a speaker does not also need the hide affordance sitting in
|
{/* Not on an audio track. The control is the old visibility eye, and
|
||||||
the eye's slot. `visible={false}` rather than omitting the element, so
|
on audio it silences rather than hides — but a row that already says
|
||||||
the spacer keeps every row's control columns aligned.
|
what it is with a speaker does not also need the hide affordance
|
||||||
|
sitting in the eye's slot. `visible={false}` rather than omitting the
|
||||||
|
element, so the spacer keeps every row's control columns aligned.
|
||||||
|
|
||||||
EXCEPT when the audio track is ALREADY hidden. Withholding the control
|
EXCEPT when the audio track is ALREADY hidden. Withholding the
|
||||||
unconditionally withheld the only way back: `data-hidden` silences the
|
control unconditionally withheld the only way back: `data-hidden`
|
||||||
clip in preview and drops it from the render, the panel's "Muted" is
|
silences the clip in preview and drops it from the render, the
|
||||||
the unrelated HTML `muted` attribute, and nothing else writes it — so a
|
panel's "Muted" is the unrelated HTML `muted` attribute, and nothing
|
||||||
track hidden before this rule (or by "Hide all", or by hand) was
|
else writes it — so a track hidden before this rule (or by "Hide
|
||||||
silent with no control anywhere to restore it. Offering the eye only
|
all", or by hand) was silent with no control anywhere to restore it.
|
||||||
in that state keeps the affordance off a normal audio row while
|
Offering the eye only in that state keeps the affordance off a normal
|
||||||
leaving the door open from the inside. */}
|
audio row while leaving the door open from the inside. */}
|
||||||
<VisibilityButton
|
<VisibilityButton
|
||||||
hidden={isTrackHidden}
|
hidden={isTrackHidden}
|
||||||
trackNumber={trackNumber}
|
trackNumber={trackNumber}
|
||||||
@@ -118,6 +126,7 @@ export function PlainTrackHeader({
|
|||||||
/>
|
/>
|
||||||
{trailing}
|
{trailing}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user