Build the sole-selection attachment carousel
The card face's in-place expansion (03-board-ui.md § Card face): a card with attachments, as the sole selection, grows a paged media band below its title; every other state stays compact behind the quiet paperclip. - CardCarousel owns the pure rules: the sole-selection predicate decides by identity (a sole-selected lane matches no face, no snapshot walk), and expansion is suppressed outside the animation key while a rubber band is active — a band names a set in progress, so carousels neither flicker nor animate under it. - QuickLook thumbnails generate off-main into a per-window cache keyed to survive reselection, with the Finder-icon fallback while loading and for non-previewable types; pages ride the platform paging behavior, dots (glass underlay, solid under Reduce Transparency) click to page, and a local wheel monitor turns a discrete tick into one clamped page — precise trackpad pans fall through untouched. - The expansion animates under Motion's new carouselExpansion transaction keyed narrowly on the sole-selected card; Reduce Motion goes instant. Drop-slot math and the marquee read the expanded height for free — both re-register on every size change. 928 unit tests (24 new). m5-interactions complete. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -105,6 +105,20 @@ enum Motion {
|
||||
reduced ? nil : .snappy(duration: Duration.laneResize)
|
||||
}
|
||||
|
||||
/// **The sole-selected card's carousel expanding and collapsing** — 03-board-ui.md § Motion's
|
||||
/// first named narrow key ("keyed narrowly — on the sole-selected card (carousel expansion)").
|
||||
///
|
||||
/// The structural voice, because that is what the change *is*: the card grows, and the cards
|
||||
/// below it in its masonry column slide down to make room. Nothing about it is a content reflow
|
||||
/// — the carousel's own arrival is a crossfade over the movement (`carouselAppearance`), and the
|
||||
/// movement is positional.
|
||||
///
|
||||
/// It is spelled here rather than aliased at the call site so the figure stays one decision: if
|
||||
/// the expansion ever wants a duration of its own, this is the line that changes.
|
||||
static func carouselExpansion(reduced: Bool) -> Animation? {
|
||||
structural(reduced: reduced)
|
||||
}
|
||||
|
||||
/// The content-reflow voice: search filtering and undo/redo restore, "deliberately paired so a
|
||||
/// restore reads like the search filter — leavers and arrivers run their transition, survivors
|
||||
/// reflow under one gentle spring".
|
||||
@@ -165,6 +179,20 @@ enum Motion {
|
||||
laneAppearance(reduced: reduced).transition
|
||||
}
|
||||
|
||||
/// **The attachment carousel arriving inside an already-drawn card** — the one appearance in the
|
||||
/// app with no reduced variant, because it is already the reduced form.
|
||||
///
|
||||
/// 03-board-ui.md fixes an appear/disappear *scale* for cards (~0.8) and lanes (~0.9) and for
|
||||
/// nothing else, and inventing a third would be the per-site literal this type exists to
|
||||
/// prevent. It is also unnecessary: the movement a carousel expansion performs is the card
|
||||
/// growing, which `carouselExpansion` already animates — the carousel itself only has to arrive
|
||||
/// over it, and 10-accessibility.md's word for that is crossfade. So a Reduce Motion user gets
|
||||
/// the same fade with the growth gone instant, which is exactly what the per-voice rule asks
|
||||
/// for, one accessor rather than two.
|
||||
static var carouselAppearance: Appearance { .crossfade }
|
||||
|
||||
static var carouselTransition: AnyTransition { carouselAppearance.transition }
|
||||
|
||||
// MARK: - The AppKit face
|
||||
|
||||
/// The lane-resize window animation's duration, for `NSAnimationContext` — which takes a number
|
||||
|
||||
Reference in New Issue
Block a user