fix(desktop): make message hover reveals instant

Hovering a message row currently pays a CSS fade before the action
toolbar, hover timestamp, and thread-summary pill become visible:
transition-opacity duration-150 on the action bar, transition-opacity
on the continuation timestamp, transition-colors on the row background,
and transition-[...] variants on the thread summary hover surface.
The accumulated fades read as input lag. Slack and Discord apply hover
styles with no transition, which is why their hover feels instant.

Remove the transitions from the hover-reveal paths so hover state
snaps on immediately:

- MessageRow: row background hover (transition-colors) and the
  continuation timestamp gutter (transition-opacity)
- MessageActionBar: toolbar fade (transition-opacity duration-150)
- MessageThreadSummaryRow: pill surface, reply-count text, and the
  last-reply/View-thread swap

Reply-connector border-color transitions are intentionally kept: they
animate a guide highlight, not a hover reveal.

Co-authored-by: Thomas Petersen <thomasp@squareup.com>
Signed-off-by: Thomas Petersen <thomasp@squareup.com>
This commit is contained in:
Wintermute
2026-08-18 08:53:03 -04:00
parent 978e585e8d
commit 600772c4f2
3 changed files with 8 additions and 8 deletions

View File

@@ -470,7 +470,7 @@ export const MessageActionBar = React.memo(function MessageActionBar({
return (
<div
className={cn(
"-m-1 p-1 transition-opacity duration-150 ease-out",
"-m-1 p-1",
"opacity-100 sm:pointer-events-none sm:opacity-0",
"sm:group-hover/message:pointer-events-auto sm:group-hover/message:opacity-100",
"sm:group-focus-within/message:pointer-events-auto sm:group-focus-within/message:opacity-100",

View File

@@ -500,7 +500,7 @@ export const MessageRow = React.memo(
)}
>
<MessageTimestamp
className="opacity-0 transition-opacity group-hover/message:opacity-100 group-focus-within/message:opacity-100"
className="opacity-0 group-hover/message:opacity-100 group-focus-within/message:opacity-100"
createdAt={message.createdAt}
hideDayPeriod
/>
@@ -866,7 +866,7 @@ export const MessageRow = React.memo(
<article
className={cn(
"group/message relative z-10 rounded-2xl transition-colors",
"group/message relative z-10 rounded-2xl",
playEntrance && "motion-enter-conversation",
"py-1",
hoverBackground

View File

@@ -210,7 +210,7 @@ export function MessageThreadSummaryRow({
<button
aria-label={summaryAriaLabel}
className="group relative isolate inline-flex h-[1.875rem] w-fit max-w-full cursor-pointer items-center gap-1.5 rounded-full py-0 pr-3 text-left text-xs font-medium text-muted-foreground transition-[color,opacity] hover:text-foreground hover:opacity-90 focus-visible:outline-hidden"
className="group relative isolate inline-flex h-[1.875rem] w-fit max-w-full cursor-pointer items-center gap-1.5 rounded-full py-0 pr-3 text-left text-xs font-medium text-muted-foreground hover:text-foreground hover:opacity-90 focus-visible:outline-hidden"
data-thread-head-id={message.id}
data-testid="message-thread-summary"
onClick={() => onOpenThread(message)}
@@ -223,7 +223,7 @@ export function MessageThreadSummaryRow({
>
<span
aria-hidden="true"
className="pointer-events-none absolute bottom-[-0.125rem] top-[-0.125rem] rounded-full opacity-0 ring-border/70 transition-[background-color,box-shadow,opacity] group-hover:bg-background/95 group-hover:opacity-100 group-hover:ring-1 group-focus-visible:bg-background/95 group-focus-visible:opacity-100 group-focus-visible:ring-1 group-focus-visible:ring-ring"
className="pointer-events-none absolute bottom-[-0.125rem] top-[-0.125rem] rounded-full opacity-0 ring-border/70 group-hover:bg-background/95 group-hover:opacity-100 group-hover:ring-1 group-focus-visible:bg-background/95 group-focus-visible:opacity-100 group-focus-visible:ring-1 group-focus-visible:ring-ring"
data-testid="message-thread-summary-surface"
style={{
left: surfaceInsetStart,
@@ -242,7 +242,7 @@ export function MessageThreadSummaryRow({
</div>
<div className="relative z-10 min-w-0">
<div>
<span className="font-medium transition-colors group-hover:text-foreground">
<span className="font-medium group-hover:text-foreground">
{summary.replyCount} {replyLabel}
</span>
{unreadCount != null && unreadCount > 0 ? (
@@ -257,14 +257,14 @@ export function MessageThreadSummaryRow({
</span>
<span className="inline-grid font-normal text-muted-foreground/70">
<span
className="col-start-1 row-start-1 transition-opacity group-hover:opacity-0 group-focus-visible:opacity-0"
className="col-start-1 row-start-1 group-hover:opacity-0 group-focus-visible:opacity-0"
data-testid="message-thread-summary-last-reply"
>
last reply{" "}
{formatThreadSummaryLastReplyTime(summary.lastReplyAt)}
</span>
<span
className="col-start-1 row-start-1 opacity-0 transition-opacity group-hover:opacity-100 group-focus-visible:opacity-100"
className="col-start-1 row-start-1 opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100"
data-testid="message-thread-summary-hover-action"
>
View thread