mediasfu-reactnative-expo
Version:
mediasfu-reactnative-expo – Expo-managed React Native WebRTC SDK for video conferencing, webinars, live streaming, broadcast, screen sharing, whiteboard, chat, recording, live subtitles, translation, and AI agent rooms on iOS, Android, and web. Prebuilt r
258 lines • 9.01 kB
JavaScript
// MainScreenComponent.tsx
import React, { useEffect } from 'react';
import { View, StyleSheet, Dimensions, } from 'react-native';
/**
* Type guard to determine if a child component conforms to ResizableChildOptions.
* @param child - The child to check.
* @returns True if the child is a React element with ResizableChildOptions, false otherwise.
*/
const isResizableChild = (child) => (child
&& typeof child === 'object'
&& 'props' in child
&& typeof child.props === 'object');
/**
* MainScreenComponent - Responsive split-screen layout with dynamic sizing
*
* MainScreenComponent is a React Native component that manages split-screen layouts
* with main and secondary content areas. It dynamically calculates component dimensions
* based on stacking mode, screen size, and percentage allocations, automatically adapting
* to window resizing and orientation changes.
*
* **Key Features:**
* - Dynamic split-screen layout (stacked or side-by-side)
* - Percentage-based size allocation for main component
* - Automatic dimension calculation with responsive updates
* - Screen width-based layout switching (narrow screens force stacking)
* - Control bar height adjustment
* - Component size state management via callbacks
* - Window resize/rotation handling
*
* **UI Customization:**
* This component can be replaced via `uiOverrides.mainScreenComponent` to
* provide a completely custom split-screen layout system.
*
* @component
* @param {MainScreenComponentOptions} props - Configuration options for the split-screen layout
*
* @returns {JSX.Element} Rendered split-screen container with calculated dimensions
*
* @example
* // Basic usage - 70/30 vertical split
* import React, { useState } from 'react';
* import { MainScreenComponent, ComponentSizes } from 'mediasfu-reactnative-expo';
*
* function SplitScreenMeeting() {
* const [sizes, setSizes] = useState<ComponentSizes>({
* mainHeight: 0,
* otherHeight: 0,
* mainWidth: 0,
* otherWidth: 0,
* });
*
* return (
* <MainScreenComponent
* mainSize={70}
* doStack={true}
* containerWidthFraction={1}
* containerHeightFraction={1}
* showControls={true}
* componentSizes={sizes}
* updateComponentSizes={setSizes}
* >
* <MainVideoGrid />
* <ParticipantsList />
* </MainScreenComponent>
* );
* }
*
* @example
* // Side-by-side layout (no stacking)
* <MainScreenComponent
* mainSize={60}
* doStack={false}
* containerWidthFraction={0.95}
* containerHeightFraction={0.9}
* showControls={true}
* componentSizes={sizes}
* updateComponentSizes={setSizes}
* >
* <VideoArea />
* <ChatPanel />
* </MainScreenComponent>
*
* @example
* // With custom content renderer (add divider)
* <MainScreenComponent
* mainSize={75}
* doStack={true}
* showControls={true}
* componentSizes={sizes}
* updateComponentSizes={setSizes}
* renderContent={({ defaultContent, dimensions }) => {
* const children = React.Children.toArray(defaultContent);
* return (
* <>
* {children[0]}
* <View style={{ height: 2, backgroundColor: '#007bff', width: '100%' }} />
* {children[1]}
* </>
* );
* }}
* >
* <MainContent />
* <SecondaryContent />
* </MainScreenComponent>
*
* @example
* // Using uiOverrides for complete layout replacement
* import { MyCustomSplitScreen } from './MyCustomSplitScreen';
*
* const sessionConfig = {
* credentials: { apiKey: 'your-api-key' },
* uiOverrides: {
* mainScreenComponent: {
* component: MyCustomSplitScreen,
* injectedProps: {
* animateTransitions: true,
* minMainSize: 50,
* },
* },
* },
* };
*
* // MyCustomSplitScreen.tsx
* export const MyCustomSplitScreen = (props: MainScreenComponentOptions & { animateTransitions: boolean; minMainSize: number }) => {
* const [dimensions, setDimensions] = React.useState({ width: 0, height: 0 });
*
* React.useEffect(() => {
* const calculateSizes = () => {
* const { width, height } = Dimensions.get('window');
* const actualMainSize = Math.max(props.mainSize, props.minMainSize);
* const mainHeight = props.doStack ? (height * actualMainSize) / 100 : height;
* const otherHeight = props.doStack ? height - mainHeight : height;
*
* props.updateComponentSizes({
* mainHeight,
* otherHeight,
* mainWidth: width,
* otherWidth: width,
* });
* setDimensions({ width, height });
* };
*
* const subscription = Dimensions.addEventListener('change', calculateSizes);
* calculateSizes();
* return () => subscription?.remove();
* }, [props.mainSize, props.doStack]);
*
* const children = React.Children.toArray(props.children);
* return (
* <View style={{ flex: 1, flexDirection: props.doStack ? 'column' : 'row' }}>
* {children}
* </View>
* );
* };
*/
const MainScreenComponent = ({ children, mainSize, doStack, containerWidthFraction = 1, containerHeightFraction = 1, updateComponentSizes, defaultFraction = 0.94, showControls, componentSizes, style, renderContent, renderContainer, }) => {
const { width: windowWidth, height: windowHeight } = Dimensions.get('window');
// Calculate parent dimensions based on fractions and control visibility
const parentWidth = containerWidthFraction * windowWidth;
const parentHeight = showControls
? containerHeightFraction * windowHeight * defaultFraction
: containerHeightFraction * windowHeight;
// Determine if the screen is wide
let isWideScreen = parentWidth >= 768;
if (!isWideScreen && parentWidth > 1.5 * parentHeight) {
isWideScreen = true;
}
/**
* Computes the dimensions for the main and other components based on stacking mode and screen width.
* @returns {ComponentSizes} The calculated sizes for the components.
*/
const computeDimensions = () => {
if (doStack) {
if (isWideScreen) {
return {
mainHeight: parentHeight,
otherHeight: parentHeight,
mainWidth: Math.floor((mainSize / 100) * parentWidth),
otherWidth: Math.floor(((100 - mainSize) / 100) * parentWidth),
};
}
return {
mainHeight: Math.floor((mainSize / 100) * parentHeight),
otherHeight: Math.floor(((100 - mainSize) / 100) * parentHeight),
mainWidth: parentWidth,
otherWidth: parentWidth,
};
}
return {
mainHeight: parentHeight,
otherHeight: parentHeight,
mainWidth: parentWidth,
otherWidth: parentWidth,
};
};
useEffect(() => {
const { mainHeight, otherHeight, mainWidth, otherWidth, } = computeDimensions();
updateComponentSizes({
mainHeight, otherHeight, mainWidth, otherWidth,
});
}, [parentWidth, parentHeight, mainSize, doStack, isWideScreen]);
const dimensions = {
width: parentWidth,
height: parentHeight,
};
const defaultContent = (<>
{/* Render child components with updated dimensions */}
{React.Children.map(children, (child, index) => {
if (isResizableChild(child)) {
const childStyle = doStack
? {
height: index === 0 ? componentSizes.mainHeight : componentSizes.otherHeight,
width: index === 0 ? componentSizes.mainWidth : componentSizes.otherWidth,
}
: {
height: componentSizes.mainHeight,
width: componentSizes.mainWidth,
};
return React.cloneElement(child, {
mainSize,
isWideScreen,
style: [child.props.style, childStyle],
key: index,
});
}
return null;
})}
</>);
const content = renderContent
? renderContent({ defaultContent, dimensions })
: defaultContent;
const defaultContainer = (<View style={[
styles.screenContainer,
{
flexDirection: isWideScreen ? 'row' : 'column',
width: parentWidth,
height: parentHeight,
},
style,
]}>
{content}
</View>);
return renderContainer
? renderContainer({ defaultContainer, dimensions })
: defaultContainer;
};
export default MainScreenComponent;
/**
* Stylesheet for the MainScreenComponent.
*/
const styles = StyleSheet.create({
screenContainer: {
flex: 1,
padding: 0,
margin: 0,
},
});
//# sourceMappingURL=MainScreenComponent.js.map