import { FakeLandscapeView, rotateDimensions, rotateInsets, useRotation, type ViewRotation } from '@tastic/core'
import { type ReactNode, useMemo } from 'react'
import { StyleSheet, useWindowDimensions, View } from 'react-native'
import { Portal } from 'react-native-paper'
import { SafeAreaInsetsContext, useSafeAreaInsets } from 'react-native-safe-area-context'
export interface RotatedFrameProps {
children: ReactNode
/** The angle to turn the frame by. Defaults to the live physical hold (@tastic/core's useRotation); pass it
* when the caller already has the exact angle it wants, e.g. a dialog handed its caller's rotation, or 0 for
* one that already sits inside a turned frame. */
rotation?: ViewRotation
/** Give everything inside the frame the safe-area insets already remapped onto the frame's own edges (see
* @tastic/core's rotateInsets), so a component that reads `useSafeAreaInsets()` itself (a library screen,
* Paper's Modal, a toaster) lands on the right edge when the frame is turned. Leave it off for content that
* rotates the insets itself, which would otherwise rotate them twice. */
rotatedSafeArea?: boolean
/** Mount a Paper Portal.Host INSIDE the frame, so a dialog (Paper's Portal otherwise mounts at the app root,
* outside the frame) is laid out against the frame's footprint and turned with it. The dialog must then be
* told `rotation={0}`. */
portalHost?: boolean
/** Let touches fall through the frame to whatever is beneath it, so only its children claim them. For a frame
* laid OVER other content (a dialog layer, a root-level overlay); a screen that owns the window leaves it off. */
passThrough?: boolean
/** Cap on the content column's width in the WIDE layout only (the rotated footprint is landscape), centred: a
* stats list or a roster stretched across an 874pt-wide landscape screen reads badly. Portrait is never capped,
* so a portrait iPad or desktop window keeps its full width. Omit for no cap. */
maxContentWidth?: number
}
/** A frame turned to follow the phone while the OS stays portrait-locked (see @tastic/core's FakeLandscapeView),
* sized to the turned footprint so its content lays out against the short edge at +/-90. The tree is IDENTICAL
* at every angle, so a rotation never remounts what is inside: `FakeLandscapeView > SafeArea provider >
* [Portal.Host] > column`. `rotation`, `rotatedSafeArea`, `passThrough` and `maxContentWidth` only change styles
* and values, but `portalHost` adds or removes the Portal.Host element, so it must be STATIC per call site
* (flipping it at runtime would remount everything inside).
*
* Safe for tap-driven content only: react-native-gesture-handler's translation deltas are untransformed, so
* never put a gesture-tracked game board inside it. */
export function RotatedFrame({ children, rotation: rotationOverride, rotatedSafeArea = false, portalHost = false, passThrough = false, maxContentWidth }: RotatedFrameProps) {
const ambientRotation = useRotation()
const rotation = rotationOverride ?? ambientRotation
const window = useWindowDimensions()
const { width, height } = rotateDimensions(window.width, window.height, rotation)
const rawInsets = useSafeAreaInsets()
const insets = useMemo(() => (rotatedSafeArea ? rotateInsets(rawInsets, rotation) : rawInsets), [rotatedSafeArea, rawInsets, rotation])
const cappedWidth = maxContentWidth !== undefined && width > height ? maxContentWidth : undefined
const column = (
{children}
)
return (
{portalHost ? {column} : column}
)
}
const styles = StyleSheet.create({
// A centred column: full width up to `maxWidth`, so a capped frame sits in the middle of a wider window.
column: {
alignSelf: 'center',
flex: 1,
width: '100%'
},
root: {
flex: 1
}
})