|
1 | 1 | import { TRACK_H } from "./timelineLayout"; |
2 | 2 | import { TrackClipCount } from "./TrackClipCount"; |
3 | 3 |
|
4 | | -// Layer row (Figma order: disclosure ∿, diamond, name) — the disclosure lives |
| 4 | +// Layer row (diamond, name, then the ∿ disclosure on the right edge) — the |
| 5 | +// disclosure lives |
5 | 6 | // here, not on the clip bar, and re-expands a collapsed layer. `∿` (not a |
6 | 7 | // caret) because a group's own row keeps the caret for its structural |
7 | 8 | // disclosure (member rows) — this button only ever means "show this row's |
8 | 9 | // lanes", so it needs its own distinct glyph. |
| 10 | +/** |
| 11 | + * The `∿` that shows or hides a row's lanes. |
| 12 | + * |
| 13 | + * Shared, because two layouts need the identical control: the keyframe layer |
| 14 | + * row below, and the plain track header — an audio track with automation keeps |
| 15 | + * its own look (music glyph, indent) and gains this, rather than being |
| 16 | + * re-rendered as a keyframe layer to get at the button. |
| 17 | + */ |
| 18 | +export function LaneToggleButton({ |
| 19 | + name, |
| 20 | + isExpanded, |
| 21 | + lanesId, |
| 22 | + onToggle, |
| 23 | +}: { |
| 24 | + name: string; |
| 25 | + isExpanded: boolean; |
| 26 | + lanesId: string; |
| 27 | + onToggle: () => void; |
| 28 | +}) { |
| 29 | + return ( |
| 30 | + <button |
| 31 | + type="button" |
| 32 | + // ponytail: No focus id here; keyboard routing belongs to the enclosing logical row. |
| 33 | + tabIndex={-1} |
| 34 | + aria-expanded={isExpanded} |
| 35 | + aria-controls={lanesId} |
| 36 | + aria-label={`${isExpanded ? "Hide" : "Show"} ${name} lanes`} |
| 37 | + title={`${isExpanded ? "Hide" : "Show"} lanes`} |
| 38 | + // h-6 w-6 = the 24x24 WCAG 2.2 minimum target. The glyph stays 11px; |
| 39 | + // only the hit box grows. |
| 40 | + className={`ml-auto flex h-6 w-6 shrink-0 items-center justify-center rounded border-0 bg-transparent p-0 text-[11px] leading-none focus-visible:outline focus-visible:outline-1 focus-visible:outline-[#3CE6AC] ${ |
| 41 | + isExpanded ? "text-[#3CE6AC]" : "text-white/55 hover:text-white" |
| 42 | + }`} |
| 43 | + onPointerDown={(event) => event.stopPropagation()} |
| 44 | + onClick={(event) => { |
| 45 | + event.stopPropagation(); |
| 46 | + onToggle(); |
| 47 | + }} |
| 48 | + > |
| 49 | + <span aria-hidden="true">∿</span> |
| 50 | + </button> |
| 51 | + ); |
| 52 | +} |
| 53 | + |
9 | 54 | export function LayerDisclosureRow({ |
10 | 55 | name, |
11 | 56 | clipCount, |
@@ -45,37 +90,26 @@ export function LayerDisclosureRow({ |
45 | 90 | background: gutterBackground, |
46 | 91 | }} |
47 | 92 | > |
48 | | - <button |
49 | | - type="button" |
50 | | - // ponytail: No focus id here; keyboard routing belongs to the enclosing logical row. |
51 | | - tabIndex={-1} |
52 | | - aria-expanded={isExpanded} |
53 | | - aria-controls={lanesId} |
54 | | - aria-label={`${isExpanded ? "Hide" : "Show"} ${name} lanes`} |
55 | | - title={`${isExpanded ? "Hide" : "Show"} lanes`} |
56 | | - // h-6 w-6 = the 24x24 WCAG 2.2 minimum target. The glyph stays 11px; |
57 | | - // only the hit box grows. |
58 | | - className={`flex h-6 w-6 shrink-0 items-center justify-center rounded border-0 bg-transparent p-0 text-[11px] leading-none focus-visible:outline focus-visible:outline-1 focus-visible:outline-[#3CE6AC] ${ |
59 | | - isExpanded ? "text-[#3CE6AC]" : "text-white/55 hover:text-white" |
60 | | - }`} |
61 | | - onPointerDown={(event) => event.stopPropagation()} |
62 | | - onClick={(event) => { |
63 | | - event.stopPropagation(); |
64 | | - onToggleClipExpanded(); |
65 | | - }} |
66 | | - > |
67 | | - <span aria-hidden="true">∿</span> |
68 | | - </button> |
69 | 93 | {/* Decorative: the disclosure button above already names the row's keyframe |
70 | 94 | state, and aria-label on a plain span is not exposed reliably anyway. */} |
71 | 95 | <span aria-hidden="true" className="shrink-0 text-[13px] leading-none text-white/40"> |
72 | 96 | ◇ |
73 | 97 | </span> |
74 | | - <span className="min-w-0 flex-1 truncate font-medium" title={name}> |
75 | | - {name} |
76 | | - </span> |
| 98 | + {/* Wraps rather than truncating: a truncated name needs a hover to be |
| 99 | + read, which a scanned column cannot rely on. */} |
| 100 | + <span className="min-w-0 flex-1 break-words font-medium leading-tight">{name}</span> |
77 | 101 | <TrackClipCount clipCount={clipCount} /> |
78 | 102 | {children} |
| 103 | + {/* Anchored right, on every header that has one: the lane toggle is the |
| 104 | + row's last word about itself, and a left-hand ∿ put it where the eye |
| 105 | + looks for identity instead. `ml-auto` rather than a spacer so it holds |
| 106 | + the edge whatever else the row grows. */} |
| 107 | + <LaneToggleButton |
| 108 | + name={name} |
| 109 | + isExpanded={isExpanded} |
| 110 | + lanesId={lanesId} |
| 111 | + onToggle={onToggleClipExpanded} |
| 112 | + /> |
79 | 113 | </div> |
80 | 114 | ); |
81 | 115 | } |
0 commit comments