refactor(ui): make an event row a single open target

The row already opened the event; the tick sat on top of it, so the two
smallest targets on the page did different things a few pixels apart.
"Done" was never the only thing a reader wants to say about an event
either — status, effort and notes all live in the detail sheet.

The tick becomes a chevron: decorative, aria-hidden, and leaning towards
where the row is about to take you on hover. The full-bleed button is
the only control, so there is no second stop for anyone tabbing or using
a screen reader. Undo on a revealed ignored row stays a real button —
it is a real action with nowhere better to sit.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Lucas Winther
2026-08-15 21:16:25 +02:00
co-authored by Claude Opus 5
parent b085087b05
commit b1df7b2581
3 changed files with 50 additions and 31 deletions
-2
View File
@@ -228,7 +228,6 @@ export function App() {
status={prog.progress[row.event.id]?.status}
effort={prog.progress[row.event.id]?.effort}
ignored={ignored.marks[row.event.id] !== undefined}
onToggle={toggle}
onRestore={(id) => ignored.toggle(id)}
onOpen={setOpenId}
/>
@@ -246,7 +245,6 @@ export function App() {
status={prog.progress[row.event.id]?.status}
effort={prog.progress[row.event.id]?.effort}
ignored={ignored.marks[row.event.id] !== undefined}
onToggle={toggle}
onRestore={(id) => ignored.toggle(id)}
onOpen={setOpenId}
/>
+31 -18
View File
@@ -21,7 +21,6 @@ interface EventRowProps {
effort?: Effort | undefined;
/** Only ever true when the reader has chosen to reveal ignored events. */
ignored?: boolean | undefined;
onToggle: (id: string) => void;
onRestore?: ((id: string) => void) | undefined;
onOpen: (id: string) => void;
}
@@ -32,7 +31,6 @@ export function EventRow({
status,
effort,
ignored = false,
onToggle,
onRestore,
onOpen,
}: EventRowProps) {
@@ -156,9 +154,15 @@ export function EventRow({
</div>
</div>
{/* Sits above the row target so ticking done never opens the sheet.
On a revealed ignored row this becomes the undo, which is the most
direct place to put it. */}
{/* The trailing control used to tick an event done from the list. It
does not any more: "done" was never the only thing a reader wants
to say about an event, and a tick they can hit by accident on the
way to opening it is a bad trade. The row opens the sheet, where
status, effort and notes all live; this is just the affordance
saying so.
The one exception is a revealed ignored row, where undo is a real
action with nowhere better to sit. */}
{ignored && onRestore !== undefined ? (
<button
type="button"
@@ -178,20 +182,17 @@ export function EventRow({
</svg>
</button>
) : (
<button
type="button"
onClick={() => onToggle(event.id)}
aria-pressed={completed}
aria-label={
completed ? `Mark ${event.title} not done` : `Mark ${event.title} done`
}
className={`row-check pointer-events-auto relative z-20 grid size-7 shrink-0 cursor-pointer place-items-center self-center rounded-md border ${
completed
? "border-transparent bg-near/20 text-near"
: "border-hairline text-faint"
// Decorative: the full-bleed button behind the row is already the
// control, and a second one with the same effect would just be an
// extra stop for anyone tabbing or using a screen reader.
<span
aria-hidden
className={`row-check row-open grid size-7 shrink-0 place-items-center self-center rounded-md ${
completed ? "text-near" : "text-faint"
}`}
>
<svg viewBox="0 0 16 16" className="size-3.5" aria-hidden>
{completed ? (
<svg viewBox="0 0 16 16" className="size-3.5">
<path
d="M2.5 8.5l3.5 3.5 7.5-8"
fill="none"
@@ -201,7 +202,19 @@ export function EventRow({
strokeLinejoin="round"
/>
</svg>
</button>
) : (
<svg viewBox="0 0 16 16" className="size-3.5">
<path
d="M6 3l5 5-5 5"
fill="none"
stroke="currentColor"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)}
</span>
)}
</div>
</li>
+8
View File
@@ -176,6 +176,13 @@ body {
border-color: var(--color-faint);
color: var(--color-muted);
}
/* The chevron leans towards where the row is about to take you. It is the
only thing left on the right of a row, so it has to carry "this opens"
on its own. */
.event-row:hover .row-open {
transform: translateX(2px);
color: var(--color-ink);
}
.row-check:hover {
transform: scale(1.08);
border-color: var(--color-muted);
@@ -212,6 +219,7 @@ body {
}
.row-check:hover,
.row-check:active,
.event-row:hover .row-open,
.event-row:hover .row-rail {
transform: none;
}