diff --git a/src/app/components/stream-header/StreamHeader.css.ts b/src/app/components/stream-header/StreamHeader.css.ts index cf1b8867..bb2da12d 100644 --- a/src/app/components/stream-header/StreamHeader.css.ts +++ b/src/app/components/stream-header/StreamHeader.css.ts @@ -161,16 +161,15 @@ export const segmentDot = recipe({ }, }); -// Chip row — outer clip-strip. Each row reveals from underneath the -// curtain when the user drags down to the corresponding peek stage. +// Chip row — outer clip-strip. Both rows reveal together when the +// user drags the curtain down to the `peek` snap. // // The `marginBottom` math is load-bearing for the snap-top -// calculation: the resting `top` of `peek1`/`peek2` lands the curtain -// exactly where the next row would have begun, so the breather never -// "steals" pixels from the next chip's paddingTop. Two different -// values: +// calculation: the resting `top` of `peek` lands the curtain exactly +// where the next row would have begun, so the breather never "steals" +// pixels from the next chip's paddingTop. Two different values: // - default (chip-to-chip): `CHIP_GAP_PX` — tighter, so the two -// pills read as a related pair when both are revealed at peek2. +// pills read as a related pair when both are revealed. // - `:last-child` (chip-to-curtain): `CURTAIN_BREATHER_PX` — wider, // so the curtain's rounded top has comfortable air above the // chip pill it lands above. diff --git a/src/app/components/stream-header/StreamHeader.tsx b/src/app/components/stream-header/StreamHeader.tsx index 2824e665..817a6ca1 100644 --- a/src/app/components/stream-header/StreamHeader.tsx +++ b/src/app/components/stream-header/StreamHeader.tsx @@ -237,7 +237,7 @@ export function StreamHeader({ scrollRef, children, bottomPinned }: StreamHeader iconSrc={Icons.Search} label={t('Search.search')} onClick={openSearch} - hidden={curtain.snap === 'closed'} + hidden={curtain.snap !== 'peek'} />
@@ -245,7 +245,7 @@ export function StreamHeader({ scrollRef, children, bottomPinned }: StreamHeader iconSrc={Icons.Plus} label={t('Direct.create_chat')} onClick={openChat} - hidden={curtain.snap !== 'peek2'} + hidden={curtain.snap !== 'peek'} />
diff --git a/src/app/components/stream-header/geometry.ts b/src/app/components/stream-header/geometry.ts index b6f054a0..516fcd17 100644 --- a/src/app/components/stream-header/geometry.ts +++ b/src/app/components/stream-header/geometry.ts @@ -10,8 +10,8 @@ // // Snap stops (curtain.top, px): // closed = TABS_ROW_PX -// peek1 = TABS_ROW_PX + CHIP_ROW_PX -// peek2 = TABS_ROW_PX + CHIP_ROW_PX * 2 +// peek = TABS_ROW_PX + 2·CHIP_ROW_PX + CHIP_GAP_PX +// + CURTAIN_BREATHER_PX // form:* = TABS_ROW_PX + formHeight + CURTAIN_BREATHER_PX // ──────────────────────────────────────────────────────────────────── diff --git a/src/app/components/stream-header/useCurtainGesture.ts b/src/app/components/stream-header/useCurtainGesture.ts index fb899e1f..facf87fb 100644 --- a/src/app/components/stream-header/useCurtainGesture.ts +++ b/src/app/components/stream-header/useCurtainGesture.ts @@ -7,7 +7,7 @@ import { DIRECTION_DEAD_ZONE_PX, RUBBER_BAND, } from './geometry'; -import { CurtainSnap, isFormSnap, isPeekSnap } from './useCurtainState'; +import { CurtainSnap, isFormSnap } from './useCurtainState'; type Args = { // The scroll viewport that hosts the chat list inside the curtain. @@ -29,9 +29,9 @@ type Args = { // Touch-gesture driver for the curtain. Native-only: on web/PC the // listeners aren't attached at all. // -// Peek path: drag down from `closed`/`peek1`/`peek2` rubber-bands the -// live delta and on release commits to the nearest stage. Drag UP from -// peek retreats toward `closed` via the same threshold logic. +// Peek path: drag down from `closed` rubber-bands the live delta and +// on release past the threshold commits to `peek` (both chips +// revealed in one motion). Drag UP from `peek` retreats to `closed`. // // Form-close path: drag UP from a form snap tracks the finger 1:1; on // release past `ACTIVE_CLOSE_THRESHOLD_PX` commits to `closed`. @@ -92,14 +92,14 @@ export function useCurtainGesture({ scrollRef, snap, setLiveDrag, commit }: Args if (Math.abs(delta) < DIRECTION_DEAD_ZONE_PX) return; direction = delta > 0 ? 'down' : 'up'; - // Direction guards: nothing higher than closed; nothing lower - // than peek2; form snaps only close (up). + // Direction guards: nothing higher than `closed`; nothing + // lower than `peek`; form snaps only close (up). if (currentSnap === 'closed' && direction === 'up') { startY = null; direction = null; return; } - if (currentSnap === 'peek2' && direction === 'down') { + if (currentSnap === 'peek' && direction === 'down') { startY = null; direction = null; return; @@ -143,17 +143,15 @@ export function useCurtainGesture({ scrollRef, snap, setLiveDrag, commit }: Args if (Math.abs(lastDelta) >= ACTIVE_CLOSE_THRESHOLD_PX) { next = 'closed'; } - } else if (isPeekSnap(currentSnap) || currentSnap === 'closed') { - const progressStages = lastDelta / CHIP_ROW_PX; - let baseStage = 0; - if (currentSnap === 'peek1') baseStage = 1; - else if (currentSnap === 'peek2') baseStage = 2; - if (Math.abs(progressStages) >= COMMIT_THRESHOLD) { - const target = Math.max(0, Math.min(2, Math.round(baseStage + progressStages))); - let resolved: CurtainSnap = 'closed'; - if (target === 1) resolved = 'peek1'; - else if (target === 2) resolved = 'peek2'; - next = resolved; + } else { + // Single-stage peek toggle. Threshold is COMMIT_THRESHOLD of a + // chip-row worth of rubber-banded drag — same touch effort as + // the old per-stage commit, but now flips the WHOLE peek in + // one motion (`closed` ⇄ `peek`). + const progress = lastDelta / CHIP_ROW_PX; + if (Math.abs(progress) >= COMMIT_THRESHOLD) { + if (currentSnap === 'closed' && progress > 0) next = 'peek'; + else if (currentSnap === 'peek' && progress < 0) next = 'closed'; } } diff --git a/src/app/components/stream-header/useCurtainState.ts b/src/app/components/stream-header/useCurtainState.ts index e8c37c46..d1a4398e 100644 --- a/src/app/components/stream-header/useCurtainState.ts +++ b/src/app/components/stream-header/useCurtainState.ts @@ -13,8 +13,7 @@ import { // is no separate «active vs peek» mode flag — the snap encodes both. export type CurtainSnap = | 'closed' // curtain flush under tabs row; nothing peeking - | 'peek1' // search chip revealed - | 'peek2' // search + new-chat chips revealed + | 'peek' // both action chips (search + new chat) revealed | 'form-search' // full search form revealed | 'form-chat'; // full new-chat form revealed @@ -23,8 +22,7 @@ export const isFormSnap = ( ): snap is 'form-search' | 'form-chat' => snap === 'form-search' || snap === 'form-chat'; -export const isPeekSnap = (snap: CurtainSnap): snap is 'peek1' | 'peek2' => - snap === 'peek1' || snap === 'peek2'; +export const isPeekSnap = (snap: CurtainSnap): snap is 'peek' => snap === 'peek'; // Form kind currently rendered in the header. Stays set during the // curtain's close transition so the form has content to slide behind; @@ -81,9 +79,7 @@ export function snapTopPx(snap: CurtainSnap, formH: number | null): number { switch (snap) { case 'closed': return TABS_ROW_PX; - case 'peek1': - return TABS_ROW_PX + CHIP_ROW_PX + CURTAIN_BREATHER_PX; - case 'peek2': + case 'peek': return ( TABS_ROW_PX + CHIP_ROW_PX +