style(report): draw grouped rows the way the app already draws them

The report had invented its own treatment for a group and its events: a
flat surface, a rail drawn as an inset shadow at its own width, and a
foot closing the group off. The cuesheet and the sheet import preview had
already settled this, and settled it the same way as each other, so the
report now follows them rather than adding a third dialect.

Both wash a row with a colour mixed from the entry's own colour and rail
it down the left, deepening for the group that owns the rows: 4% and 3px
for an event, 12% and 4px for the group, over gray-1300. Both read that
colour from a --entry-colour custom property, which is what the report
now sets too. The import preview is the closer precedent of the two,
being a table of grouped entries inside a settings panel, so its exact
mix ratios and rail widths are the ones taken here.

Groups are separated by space above the heading, as the preview does it,
which retires the closing foot. The summary card's surface moves behind a
--card-surface variable so the group tier can wash it without a
specificity fight, and the neutral default stays the gray-1200 that Info
uses. The wash sits on the cells rather than the row so it covers the
panel table's own striping, which no longer shows through on some rows
and not others.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019nr3FbLbM8gB8Jm771YgTV
This commit is contained in:
Claude
2026-08-26 06:14:10 +00:00
parent ef472b513a
commit 255695bc4a
3 changed files with 38 additions and 53 deletions
@@ -1,7 +1,9 @@
// an inset surface within the panel card, matching how Info nests inside one
// an inset surface within the panel card, matching how Info nests inside one.
// The surface is a variable so a tier with a colour of its own can wash it
// without having to out-specify this rule
.card {
padding: 1.25rem;
background-color: $gray-1200;
background-color: var(--card-surface, #{$gray-1200});
border-radius: 3px;
display: flex;
@@ -1,7 +1,12 @@
// the rail the whole group hangs off, falling back to a neutral edge when the
// user gave the group no colour of its own
$rail-width: 3px;
$rail-colour: var(--group-colour, #{$gray-500});
// Rows follow the treatment the cuesheet and the sheet import preview already
// use for entries: a wash of the entry's own colour over the row and a rail of
// it down the left, deepening for the group that owns them. --entry-colour is
// the variable both of those read.
//
// The two fall back differently on purpose: a group the user gave no colour
// still needs a visible edge to be read as a group, but nothing to wash with.
$rail: var(--entry-colour, #{$gray-500});
$wash: var(--entry-colour, transparent);
// event rows carry their values in td, not th: the panel's th styling is meant
// for column headings and renders whatever it holds small, bold and upper case
@@ -13,44 +18,33 @@ td.under {
color: $playback-under;
}
// the group heading is a card rather than a table row treatment, so the cell
// only has to give it room and stay out of the table's striping
.groupRow {
background-color: transparent;
td {
padding: 1.25rem 0 0;
}
// the surface sits on the cells rather than the row so it paints over the
// table's own striping without having to out-specify it
.eventRow td {
background-color: color-mix(in srgb, #{$gray-1300} 96%, #{$wash} 4%);
}
// a continuous coloured edge down the heading and every event under it, closed
// by the cap below. Mirrors how the rundown editor brackets a group, so the
// report reads with the same grammar as the rundown it reports on
.grouped {
box-shadow: inset $rail-width 0 $rail-colour;
}
td.grouped {
.groupedRow td:first-child {
box-shadow: inset 3px 0 $rail;
// clear the rail rather than sitting against it
padding-left: 0.75rem;
}
.groupEndRow {
// the group heading is a card rather than a row treatment, so the cell only has
// to give it room. The space above it is what separates one group from the last
.groupRow {
background-color: transparent;
td {
padding: 0 0 1.25rem;
padding: 0;
border-top: 1.25rem solid transparent;
}
}
.groupEnd {
display: block;
height: 0.5rem;
background-color: $rail-colour;
border-radius: 0 0 $rail-width $rail-width;
// the rail tapering off, not a second element: any wider and the foot reads
// as an underline belonging to the last event rather than to the group
width: $rail-width;
.groupCard {
--card-surface: color-mix(in srgb, #{$gray-1300} 88%, #{$rail} 12%);
box-shadow: inset 4px 0 $rail;
}
.eventCue,
@@ -44,7 +44,6 @@ export default function ReportTable({ rows, groups }: ReportTableProps) {
{section.rows.map((entry) => (
<EventRow key={entry.id} entry={entry} colour={section.group?.colour} />
))}
{section.group && <GroupEndRow colour={section.group.colour} />}
</Fragment>
))}
</tbody>
@@ -62,10 +61,10 @@ function GroupRow({ group }: { group: GroupReport }) {
const measuredAgainst = group.targetDuration ?? group.scheduledDuration;
return (
<tr className={style.groupRow}>
<td colSpan={7} style={groupColour(group.colour)}>
<tr className={style.groupRow} style={entryColour(group.colour)}>
<td colSpan={7}>
<ReportSummaryCard
className={style.grouped}
className={style.groupCard}
title={group.title || 'Untitled group'}
subtitle={
group.actualStart !== null && group.actualEnd !== null
@@ -116,8 +115,8 @@ function EventRow({ entry, colour }: { entry: CombinedReport; colour?: string })
const grouped = colour !== undefined;
return (
<tr style={grouped ? groupColour(colour) : undefined}>
<td className={cx([style.eventIndex, grouped && style.grouped])}>{entry.index}</td>
<tr className={cx([style.eventRow, grouped && style.groupedRow])} style={grouped ? entryColour(colour) : undefined}>
<td className={style.eventIndex}>{entry.index}</td>
<td className={style.eventCue}>{entry.cue}</td>
<td>{entry.title}</td>
<td>{formatTime(entry.scheduledStart)}</td>
@@ -129,22 +128,12 @@ function EventRow({ entry, colour }: { entry: CombinedReport; colour?: string })
}
/**
* Closes the group off the way the rundown editor does, so the events between
* the heading and this cap read as belonging to it rather than following it.
* The entry's own colour, read by the row styling the same way the cuesheet and
* the import preview read it. Left unset when the user gave the group none, so
* the stylesheet falls back to a neutral edge.
*/
function GroupEndRow({ colour }: { colour: string }) {
return (
<tr className={style.groupEndRow} style={groupColour(colour)}>
<td colSpan={7}>
<span className={style.groupEnd} />
</td>
</tr>
);
}
/** The group's own colour, or a neutral rail when the user set none */
function groupColour(colour?: string): React.CSSProperties {
return { '--group-colour': colour || undefined } as React.CSSProperties;
function entryColour(colour?: string): React.CSSProperties {
return { '--entry-colour': colour || undefined } as React.CSSProperties;
}
/** Whether an actual time landed before (under) or after (over) its schedule */