vojo/src/app/pages/client/channels/ChannelsWorkspaceHorseshoe.css.ts

139 lines
5.2 KiB
TypeScript

import { style } from '@vanilla-extract/css';
import { color, toRem } from 'folds';
import { VOJO_HORSESHOE_GAP_PX, VOJO_HORSESHOE_RADIUS_PX } from '../../../styles/horseshoe';
// Re-exported so the TSX can pick up the constants without crossing the
// vanilla-extract / runtime boundary twice. Mirror of the canonical
// MobileSettingsHorseshoe css module.
export const HORSESHOE_RADIUS_PX = VOJO_HORSESHOE_RADIUS_PX;
export const HORSESHOE_GAP_PX = VOJO_HORSESHOE_GAP_PX;
// Outer container — anchor for the two absolutely-positioned panes
// (`appBody` and `silhouette`). `flex: 1` fills PageNav's inner column
// slot. `overflow: hidden` clips the rounded carves against the
// container's bg, which is painted inline with the void colour when
// the sheet is active. See MobileSettingsHorseshoe.css.ts for the full
// rationale on every property here — kept verbatim except for the file
// header.
//
// `marginTop: -var(--vojo-safe-top)` extends the container UP over the
// status-bar safe-top zone, and the compensating
// `paddingTop: var(--vojo-safe-top)` on `appBody` keeps the wrapped
// channels list anchored at the same visual Y. Mirror of the same
// pair in `MobileSettingsHorseshoe.css.ts::container` — purpose is
// (1) the workspace-sheet clip-path mask on `appBody` carves into an
// opaque surface that already paints through the status-bar strip,
// and (2) `appBody`'s bg paints the safe-top strip in the same
// `SurfaceVariant.Container` tone as the pager's static header so the
// system-tray backdrop stays consistent across surfaces.
//
// The curtain itself never paints into the safe-top zone — pin
// gesture clamps it at `top: 0` of the stage, see
// `components/stream-header/geometry.ts::PIN_TRAVEL_PX`.
export const container = style({
position: 'relative',
display: 'flex',
flex: 1,
flexDirection: 'column',
minWidth: 0,
minHeight: 0,
overflow: 'hidden',
marginTop: 'calc(-1 * var(--vojo-safe-top, 0px))',
});
// Wrapped children (StreamHeader → ChannelsList → WorkspaceFooter).
// Stays put — the bottom is carved away by an animated
// `clip-path: inset(...)` with rounded BL/BR. `backgroundColor` is
// load-bearing: the container is painted with the void colour when the
// sheet is active, so without an opaque bg the void would bleed through
// every transparent gap between list rows. See canonical for the full
// reasoning on clip-path vs translate vs flex-shrink.
//
// `paddingTop: var(--vojo-safe-top)` reserves status-bar space INSIDE
// appBody — compensates the `container.marginTop: -safe-top` shift so
// the wrapped flex children (StreamHeader.stage) stay anchored at the
// same visual Y as before. `backgroundColor` is `SurfaceVariant.Container`
// (not `Background.Container`) so the now-visible safe-top zone of
// appBody matches the tone `PageNav-inner` shows in Bots / Channels-root
// (which use `surface="surfaceVariant"`), giving the curtain's darker
// `Background.Container` tone a visible strip to over-paint on drag-up.
export const appBody = style({
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
display: 'flex',
flexDirection: 'column',
minWidth: 0,
minHeight: 0,
backgroundColor: color.SurfaceVariant.Container,
paddingTop: 'var(--vojo-safe-top, 0px)',
willChange: 'clip-path',
});
// Workspace switcher sheet surface. Anchored at the bottom of the
// container; height animates 0 → railHeight as the user drags / clicks.
// `SurfaceVariant.Container` matches the chat-pane tone and keeps the
// safe-area / handle gaps from reading as dark stripes on edge-to-edge
// Android — same rationale as the canonical settings horseshoe.
export const silhouette = style({
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
backgroundColor: color.SurfaceVariant.Container,
willChange: 'height, border-top-left-radius, border-top-right-radius',
});
// Top-anchored panel content. Padding-bottom reserves Android nav-bar
// inset so the create-space row never tucks under the gesture pill.
export const panelContent = style({
position: 'absolute',
top: 0,
left: 0,
right: 0,
boxSizing: 'border-box',
display: 'flex',
flexDirection: 'column',
paddingBottom: 'env(safe-area-inset-bottom, 0px)',
});
// 20px drag-to-close band at the top of the silhouette. The ONLY
// drag-down origin once the sheet is open — touches on the spaces list
// below this strip are not drag-sensitive, so internal scroll keeps
// working without a gesture conflict.
export const panelHandle = style({
flexShrink: 0,
height: toRem(20),
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'grab',
touchAction: 'none',
userSelect: 'none',
selectors: {
'&:active': { cursor: 'grabbing' },
},
});
export const panelHandleBar = style({
width: toRem(36),
height: toRem(4),
borderRadius: toRem(4),
backgroundColor: color.Background.Container,
});
// Holds the workspace switcher list. `flex: 1` so it grows below the
// 20px handle to fill the remaining panel height; internal Scroll inside
// the switcher handles overflow when the user has many orphan spaces.
export const panelBody = style({
flex: 1,
display: 'flex',
flexDirection: 'column',
minHeight: 0,
minWidth: 0,
});