vojo/src/app/pages/HorseshoeContainer.tsx

61 lines
2.7 KiB
TypeScript

// App-shell wrapper hosting the optional top share-target banner, the
// `ClientLayout` body, and the bottom call-rail (incoming-ring strips +
// active-call status pill) inside a single flex column.
//
// While at least one ring is queued or an active call's pill is visible,
// the container paints `#090909` in the gap and pulls the app shell up
// with rounded bottom corners.
//
// While `pendingShareAtom` is non-null, `ShareTargetStrip` renders above
// `appShell` and pushes ClientLayout (including its nav-tab header)
// down by the banner's height. Returns null when no share is pending,
// so the slot is free in steady state.
//
// The TOP horseshoe (user profile sheet) used to live here too, but
// was hoisted into `features/room/RoomViewProfilePanel.tsx` so it
// only affects the room/chat column — on desktop it no longer
// blankets the sidebar / nav rails.
import React, { ReactNode } from 'react';
import classNames from 'classnames';
import { useAtomValue } from 'jotai';
import { isRingingAtom } from '../state/incomingCalls';
import { IncomingCallStripRenderer } from './IncomingCallStripRenderer';
import { CallStatusRenderer, useCallStatusVisible } from './CallStatusRenderer';
import { ShareTargetStrip } from '../features/share-target';
import * as css from './HorseshoeContainer.css';
type HorseshoeContainerProps = {
children: ReactNode;
};
export function HorseshoeContainer({ children }: HorseshoeContainerProps) {
const ringing = useAtomValue(isRingingAtom);
// Gate the bottom horseshoe on whether the rail will *actually* paint
// pixels. A naive `callEmbed !== undefined` check would also flip
// true while the pill is suppressed (mobile + viewing the call room
// + video) — that would leave a black 8px shelf with no visible
// rail underneath. Reusing the same visibility predicate as the
// renderer keeps shell + content in sync.
const pillVisible = useCallStatusVisible();
const callPresent = ringing || pillVisible;
return (
<div className={classNames(css.surface, callPresent && css.surfaceActive)}>
{/* Share-target banner — rendered before appShell so it occupies its
own flex row at the top and pushes ClientLayout (including the
nav-tab header) down by the banner's height while a share is
pending. Self-renders null when no share, so this slot is free
in steady state. */}
<ShareTargetStrip />
<div className={classNames(css.appShell, callPresent && css.appShellBottomRound)}>
{children}
</div>
<div className={classNames(css.bottomRail, callPresent && css.bottomRailActive)}>
<IncomingCallStripRenderer />
<CallStatusRenderer />
{ringing && <div className={css.ringOrbit} aria-hidden />}
</div>
</div>
);
}