mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-12 21:02:17 +02:00
feat(email): Superhuman-style shortcuts, smooth scrolling & fast animations
List: j/k/arrow cursor with focus ring, Enter/o open, e archive, # trash, u read toggle, c/n compose, / search, g+i / g+d view switch, ? help overlay, layered Escape (suggestions > link bar > composer > thread > search). Thread: r / a / f open reply, reply-all (with fallback), forward. Composer: Cmd/Ctrl+Enter send (commits half-typed recipients), Cmd/Ctrl+ Shift+C/B reveal+focus Cc/Bcc, Esc closes with draft autosave. Perf: content-visibility row virtualization, pointer-events suppression while scrolling, memoized ThreadRow so cursor moves and page appends only re-render affected rows. Animations: 160ms row slide-out on archive/trash/draft-delete with snap-back on failure, fast thread-detail and inline-composer open transitions, 140ms ease-out animated scrolling for cursor-follow and thread-open (respects prefers-reduced-motion). Also fixes stale search-result rows on archive/read actions and Escape closing the whole compose modal while a suggestion menu or link bar was open. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
063f6892a8
commit
357da440c6
2 changed files with 768 additions and 101 deletions
|
|
@ -224,6 +224,46 @@
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Native list virtualization: offscreen rows skip layout and paint entirely.
|
||||
Applied only to rows without a mounted ThreadDetail (those hold iframes and
|
||||
composers, which must keep rendering while offscreen). */
|
||||
.gmail-row-group-cv {
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: auto 40px;
|
||||
}
|
||||
|
||||
/* While the list is scrolling, rows ignore the pointer so hover restyles and
|
||||
prefetch timers don't compete with frame rendering. */
|
||||
.gmail-shell[data-scrolling] .gmail-row-shell {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Archived/trashed rows slide out and collapse before removal. Removing the
|
||||
class (failed action) snaps the row back. */
|
||||
.gmail-row-group-leaving {
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
animation: gmail-row-leave 160ms ease-in forwards;
|
||||
}
|
||||
|
||||
@keyframes gmail-row-leave {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
max-height: 48px;
|
||||
}
|
||||
60% {
|
||||
opacity: 0;
|
||||
transform: translateX(32px);
|
||||
max-height: 48px;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translateX(32px);
|
||||
max-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.gmail-list-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
|
|
@ -338,6 +378,20 @@
|
|||
background: var(--gm-bg-row-selected-hover);
|
||||
}
|
||||
|
||||
/* The j/k keyboard cursor. Declared after the hover rules (same specificity)
|
||||
so the focus ring survives hovering the focused row. */
|
||||
.gmail-row-focused,
|
||||
.gmail-row-focused:hover {
|
||||
background: var(--gm-bg-row-hover);
|
||||
box-shadow: inset 0 0 0 1px var(--gm-accent);
|
||||
}
|
||||
|
||||
.gmail-row-selected.gmail-row-focused,
|
||||
.gmail-row-selected.gmail-row-focused:hover {
|
||||
background: var(--gm-bg-row-selected);
|
||||
box-shadow: inset 2px 0 0 var(--gm-accent), inset 0 0 0 1px var(--gm-accent);
|
||||
}
|
||||
|
||||
.gmail-row-unread {
|
||||
color: var(--gm-text);
|
||||
}
|
||||
|
|
@ -413,12 +467,50 @@
|
|||
border-top: 1px solid var(--gm-border);
|
||||
border-bottom: 1px solid var(--gm-border);
|
||||
box-shadow: inset 2px 0 0 var(--gm-accent);
|
||||
/* Replays whenever the detail is shown — hidden details are display: none,
|
||||
so un-hiding restarts the animation. */
|
||||
animation: gmail-detail-open 140ms ease-out;
|
||||
}
|
||||
|
||||
.gmail-detail-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@keyframes gmail-detail-open {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-6px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* The inline reply/forward composer pops in from below the thread. */
|
||||
.gmail-compose-inline {
|
||||
animation: gmail-compose-open 140ms ease-out;
|
||||
}
|
||||
|
||||
@keyframes gmail-compose-open {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.gmail-detail-inline,
|
||||
.gmail-compose-inline,
|
||||
.gmail-row-group-leaving {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
.gmail-detail-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue