158 lines
7.5 KiB
TypeScript
158 lines
7.5 KiB
TypeScript
import { style } from '@vanilla-extract/css';
|
|
import { color } from 'folds';
|
|
|
|
// Pager root. Sits inside the authed shell's row-flex slot
|
|
// (ClientLayout → Box grow=Yes), so `flex: 1 1 0` fills the slot
|
|
// horizontally; `align-items: stretch` on the parent fills vertically.
|
|
//
|
|
// `touch-action: pan-y` lets the browser keep doing native vertical
|
|
// scroll (DM list virtualizer, curtain peek pull-down) without us
|
|
// having to call preventDefault on every move — only the pager's own
|
|
// listener calls preventDefault, and only after axis-resolve commits
|
|
// to "horizontal".
|
|
//
|
|
// `SurfaceVariant.Container` backdrop intentionally shows through
|
|
// (a) the inter-pane gap during a swipe — the gap colour the user
|
|
// asked for is "light blue same as the header", which IS this
|
|
// SurfaceVariant tone — and (b) any sub-pixel rounding seam at rest.
|
|
//
|
|
// `color: Background.OnContainer` mirrors what the route-level
|
|
// `<PageRoot>` would have set via its `ContainerColor({ variant:
|
|
// 'Background' })` wrapper. We mount Direct/Channels/Bots directly
|
|
// here, bypassing PageRoot for the swipe-pager experience, so without
|
|
// this declaration the form labels rendered inside the per-pane
|
|
// StreamHeader curtain (`<Text size="L400">` for Username/Server/
|
|
// Options) had `color: inherit` cascading all the way up to `body`,
|
|
// which sets no color → browser default black. The labels became
|
|
// invisible against the dark form background only on native (where the
|
|
// pager activates). Desktop / web go through PageRoot and inherit the
|
|
// expected light tone for free.
|
|
export const pagerRoot = style({
|
|
position: 'relative',
|
|
flex: '1 1 0',
|
|
minWidth: 0,
|
|
minHeight: 0,
|
|
height: '100%',
|
|
overflow: 'hidden',
|
|
touchAction: 'pan-y',
|
|
backgroundColor: color.SurfaceVariant.Container,
|
|
color: color.Background.OnContainer,
|
|
});
|
|
|
|
// Shared static tabs row painted BEHIND the strip in DOM order.
|
|
// Reserves the status-bar safe-area inset via padding-top so the
|
|
// segments + icons sit just below the system status bar, and so the
|
|
// backdrop colour extends through the inset zone (matching the per-pane
|
|
// PageNav's own `paddingTop: var(--vojo-safe-top)` so there's no
|
|
// visible band boundary at the inset edge).
|
|
//
|
|
// Curtain-overlay invariants (why no z-index here at rest):
|
|
//
|
|
// The chats curtain must visually rise ABOVE this header when the
|
|
// user pulls it up to the «pinned» snap — like a real blind sliding
|
|
// over the segments rather than the segments moving. The curtain
|
|
// lives inside each pane > stage with `z: 2` in stage's local
|
|
// stacking context. The stage / pane stack inside the swipe `strip`,
|
|
// which creates its own stacking context via `transform`. To let the
|
|
// curtain visually surface above this static header we:
|
|
//
|
|
// (a) leave both elements at `z-index: auto` in pagerRoot's
|
|
// stacking context (this block has no `zIndex` AT REST), so
|
|
// painting order falls back to DOM order. `pagerStaticHeader`
|
|
// is rendered BEFORE `strip` in MobileTabsPager — so the
|
|
// strip (and everything inside it that paints opaquely) paints
|
|
// on top.
|
|
//
|
|
// (b) tag the strip with `data-pager-pane="true"`. All per-pane
|
|
// background paints (PageNav-inner surface, MobileSettings/
|
|
// ChannelsWorkspace appBody, StreamHeader stage + header)
|
|
// become transparent under that selector, so the static
|
|
// header tabs show through every transparent layer of the
|
|
// strip until the curtain — the only remaining opaque element
|
|
// — covers them by being positioned at top: 0 (pinned snap).
|
|
//
|
|
// Breaking (a) or (b) re-introduces the «paravozik» regression where
|
|
// the tabs visually slide with the curtain. See git history for the
|
|
// user-feedback trail.
|
|
//
|
|
// Conditional z-elevation (horseshoe-active override, suppressed by pin):
|
|
//
|
|
// When a horseshoe sheet (Settings or workspace switcher) is
|
|
// geometrically active — i.e. `expandedPx > 0`, which covers both
|
|
// the in-flight drag and the committed-open state — the wrapping
|
|
// container paints `VOJO_HORSESHOE_VOID_COLOR` (= #000 in dark
|
|
// theme) across the entire pane so the carve at the sheet's top
|
|
// reads as a dark seam. With the transparent strip stack from (b),
|
|
// that void would bleed up through the safe-top + tabsRow zone,
|
|
// turning the system-tray strip + tabs solid black.
|
|
//
|
|
// `MobileTabsPagerHeader.tsx` bumps this element to a positive
|
|
// `zIndex` (inline style, driven by `mobileHorseshoeActiveAtom`)
|
|
// from the first frame of drag. Positive z beats the strip's
|
|
// `z: auto` stacking context, putting the static header back on
|
|
// top in the safe-top + tabsRow band — the void is contained to
|
|
// the carve area, tabs stay visible. The horseshoe's `appBody`
|
|
// flips back to opaque on the same signal so the void doesn't
|
|
// bleed into the chip-area band between the static header and
|
|
// the curtain top either. The curtain pin gesture is gated off
|
|
// in the same state (see `StreamHeader.gestureDisabled`) so no
|
|
// pin can race the elevation flip.
|
|
//
|
|
// Pinned-override: when the active pane's curtain is pinned, the
|
|
// curtain itself sits at the top of the stage (z:2 inside the
|
|
// strip's stacking ctx) and covers everything from `y = safe-top`
|
|
// downward — including the tabsRow band. Above the curtain
|
|
// (y=0..safe-top) the opaque appBody contains the void. Elevating
|
|
// the static header in that state would visibly slice the pinned
|
|
// curtain in the tabsRow band, popping tabs over what the user
|
|
// explicitly pulled up to cover. So `MobileTabsPagerHeader`
|
|
// suppresses elevation whenever `curtainPinnedByTabAtom[activeTab]`
|
|
// is true — preserves the «pinned hides tabs» invariant across
|
|
// sheet open/drag without re-introducing the void leak.
|
|
export const pagerStaticHeader = style({
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
paddingTop: 'var(--vojo-safe-top, 0px)',
|
|
// The wrapped tabsRow has its own height of TABS_ROW_PX via the
|
|
// stream-header recipe; we don't set a fixed height here so the
|
|
// status-bar inset adds on top naturally.
|
|
backgroundColor: color.SurfaceVariant.Container,
|
|
});
|
|
|
|
// Horizontal strip carrying all three panes side-by-side. Width &
|
|
// transform are computed inline in the JSX (they depend on tabs.length
|
|
// and visualIdx + visualDragPx, and the gap math couples to them).
|
|
//
|
|
// `gap: PANE_GAP_PX` is what makes the inter-pane void visible during
|
|
// a swipe — the pagerRoot's SurfaceVariant.Container colour shows
|
|
// through the gap, matching the static header tone exactly.
|
|
export const strip = style({
|
|
display: 'flex',
|
|
flexDirection: 'row',
|
|
height: '100%',
|
|
willChange: 'transform',
|
|
});
|
|
|
|
// Each pane is exactly one viewport wide. CRITICALLY `display: flex;
|
|
// flex-direction: row` so the nested Folds PageNav (which is a flex
|
|
// child with `flex-grow: 1` on mobile to override its 256px recipe
|
|
// width) expands to fill the pane. A column-flex parent here would
|
|
// leave PageNav at 256px — the bug that ate the previous attempt.
|
|
//
|
|
// No paddingTop here: the per-pane StreamHeader still renders its
|
|
// own tabs row (kept for the curtain's TABS_ROW_PX snap math, just
|
|
// painted invisible via visibility:hidden), and PageNav's inner
|
|
// column reserves the status-bar safe-area inset via its own
|
|
// `paddingTop: var(--vojo-safe-top)`. The static header overlay at
|
|
// the pager root simply paints OVER the same screen zone, so the
|
|
// underlying geometry stays identical to non-pager mode.
|
|
export const pane = style({
|
|
display: 'flex',
|
|
flexDirection: 'row',
|
|
flexShrink: 0,
|
|
width: '100vw',
|
|
height: '100%',
|
|
minWidth: 0,
|
|
});
|