UNPKG

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

378 lines (377 loc) 13.5 kB
import React from 'react'; import { ScrollView, View, Text, Pressable, StyleSheet, } from 'react-native'; import { FontAwesome } from '@expo/vector-icons'; import { generatePageContent } from '../../consumers/generatePageContent'; /** * Pagination - Multi-page navigation with breakout room support * * Pagination is a React Native component for navigating between multiple pages * of content (typically participant grids). It includes special logic for breakout * room scenarios where access to certain pages is restricted based on user role. * * **Key Features:** * - Multi-page navigation with numbered buttons * - Breakout room access control * - Flexible layout positioning (top/middle/bottom, left/middle/right) * - Horizontal or vertical button arrangement * - Active page highlighting * - Host-specific room switching capabilities * - Customizable button styling * * **UI Customization:** * This component can be replaced via `uiOverrides.paginationComponent` to * provide a completely custom pagination implementation. * * @component * @param {PaginationOptions} props - Configuration options for the Pagination component * * @returns {JSX.Element} Rendered pagination controls * * @example * // Basic usage - Simple page navigation * import React from 'react'; * import { Pagination } from 'mediasfu-reactnative-expo'; * * function VideoGridWithPagination() { * const [currentPage, setCurrentPage] = React.useState(1); * * const paginationParams = { * mainRoomsLength: 3, * memberRoom: 1, * breakOutRoomStarted: false, * breakOutRoomEnded: false, * member: 'user123', * breakoutRooms: [], * hostNewRoom: 0, * roomName: 'MainRoom', * islevel: '1', * socket: socketInstance, * getUpdatedAllParams: () => paginationParams, * }; * * return ( * <Pagination * totalPages={5} * currentUserPage={currentPage} * position="middle" * location="bottom" * direction="horizontal" * backgroundColor="#ffffff" * paginationHeight={40} * parameters={paginationParams} * /> * ); * } * * @example * // With custom styling and breakout rooms * <Pagination * totalPages={10} * currentUserPage={3} * handlePageChange={async (options) => { * console.log('Changing to page:', options.page); * await generatePageContent(options); * }} * position="right" * location="top" * direction="vertical" * buttonsContainerStyle={{ gap: 8 }} * activePageStyle={{ backgroundColor: '#007bff', borderRadius: 8 }} * inactivePageStyle={{ backgroundColor: '#e0e0e0', borderRadius: 8 }} * backgroundColor="#f5f5f5" * paginationHeight={50} * showAspect={true} * parameters={{ * ...paginationParams, * breakOutRoomStarted: true, * breakoutRooms: breakoutParticipantGroups, * islevel: '2', // Host level * }} * /> * * @example * // Using uiOverrides for complete pagination replacement * import { MyCustomPagination } from './MyCustomPagination'; * * const sessionConfig = { * credentials: { apiKey: 'your-api-key' }, * uiOverrides: { * paginationComponent: { * component: MyCustomPagination, * injectedProps: { * theme: 'minimal', * showPageNumbers: true, * }, * }, * }, * }; * * // MyCustomPagination.tsx * export const MyCustomPagination = (props: PaginationOptions & { theme: string; showPageNumbers: boolean }) => { * return ( * <View style={{ flexDirection: 'row', justifyContent: 'center' }}> * <Button title="Prev" onPress={() => props.handlePageChange?.({ page: props.currentUserPage - 1, parameters: props.parameters })} /> * {props.showPageNumbers && <Text>{props.currentUserPage} / {props.totalPages}</Text>} * <Button title="Next" onPress={() => props.handlePageChange?.({ page: props.currentUserPage + 1, parameters: props.parameters })} /> * </View> * ); * }; * hostNewRoom: 2, * roomName: 'Room A', * islevel: '2', * showAlert: (alert) => console.log(alert.message), * socket: /* Socket connection * /, * getUpdatedAllParams: () => parameters, * }; * return ( * <Pagination * totalPages={10} * currentUserPage={1} * parameters={parameters} * backgroundColor="lightgray" * paginationHeight={50} * direction="horizontal" * position="middle" * location="bottom" * /> * ); * } * * export default App; * ``` */ const Pagination = ({ totalPages, currentUserPage, handlePageChange = generatePageContent, position = 'middle', location = 'middle', direction = 'horizontal', activePageStyle = { backgroundColor: '#2c678f' }, inactivePageStyle, backgroundColor = '#ffffff', paginationHeight = 40, showAspect = true, parameters, style, renderContent, renderContainer, }) => { const { mainRoomsLength, memberRoom, breakOutRoomStarted, breakOutRoomEnded, member, breakoutRooms, hostNewRoom, roomName, islevel, showAlert, socket, } = parameters; // Generate data for FlatList const data = Array.from({ length: totalPages + 1 }, (_, index) => ({ id: `${index}`, number: index, })); /** * Handles the page button click. * * @param {number} page - The page number that was clicked. */ const onPagePress = async (page) => { const updatedParameters = parameters.getUpdatedAllParams?.() ?? parameters; const { mainRoomsLength, memberRoom, breakOutRoomStarted, breakOutRoomEnded, member, breakoutRooms, hostNewRoom, roomName, islevel, showAlert, socket, } = updatedParameters; if (page === currentUserPage) { return; } if (breakOutRoomStarted && !breakOutRoomEnded && page !== 0) { const roomMember = breakoutRooms.find((r) => r.find((p) => p.name === member)); const pageInt = page - mainRoomsLength; let memberBreakRoom = -1; if (roomMember) { memberBreakRoom = breakoutRooms.indexOf(roomMember); } if ((memberBreakRoom === -1 || memberBreakRoom !== pageInt) && pageInt >= 0) { if (islevel !== '2') { if (showAlert) { showAlert({ message: `You are not part of the breakout room ${pageInt + 1}.`, type: 'danger', }); } return; } await handlePageChange({ page, parameters: updatedParameters, breakRoom: pageInt, inBreakRoom: true, }); if (hostNewRoom !== pageInt) { socket.emit('updateHostBreakout', { newRoom: pageInt, roomName }, () => { }); } } else { await handlePageChange({ page, parameters: updatedParameters, breakRoom: pageInt, inBreakRoom: pageInt >= 0, }); if (islevel === '2' && hostNewRoom !== -1) { socket.emit('updateHostBreakout', { prevRoom: hostNewRoom, newRoom: -1, roomName }, () => { }); } } } else { await handlePageChange({ page, parameters: updatedParameters, breakRoom: 0, inBreakRoom: false, }); if (islevel === '2' && hostNewRoom !== -1) { socket.emit('updateHostBreakout', { prevRoom: hostNewRoom, newRoom: -1, roomName }, () => { }); } } }; /** * Renders each page item. * * @param {PageItem} item - The page item to render. * @returns {JSX.Element} The rendered page button. */ const renderItem = ({ item }) => { const isActive = item.number === currentUserPage; const pageStyle = isActive ? [styles.activePage, activePageStyle] : [styles.inactivePage, inactivePageStyle]; let displayItem = item.number; const targetPage = memberRoom; if (breakOutRoomStarted && !breakOutRoomEnded && item.number >= mainRoomsLength) { const roomNumber = item.number - (mainRoomsLength - 1); if (targetPage + 1 !== roomNumber) { if (islevel !== '2') { displayItem = (<View style={styles.lockContainer}> <Text style={styles.pageText}> Room {roomNumber} </Text> <FontAwesome name="lock" size={16} style={styles.lockIcon}/> </View>); } else { displayItem = (<Text style={styles.pageText}> Room {roomNumber} </Text>); } } else { displayItem = (<Text style={styles.pageText}> Room {roomNumber} </Text>); } } else { // Wrap item.number in a Text component to avoid the error displayItem = <Text style={styles.pageText}>{item.number}</Text>; } return (<Pressable key={item.id} style={[styles.pageButton, pageStyle]} onPress={() => onPagePress(item.number)} accessibilityRole="button" accessibilityLabel={`Page ${item.number === 0 ? 'Star' : item.number}`}> {item.number === 0 ? (<FontAwesome name="star" size={18} color={isActive ? 'yellow' : 'gray'}/>) : (displayItem)} </Pressable>); }; /** * Determines the alignment styles based on position and location props. * * @returns StyleProp<ViewStyle> - The alignment style object. */ const getAlignmentStyle = () => { const alignmentStyle = {}; switch (position) { case 'left': alignmentStyle.justifyContent = 'flex-start'; break; case 'right': alignmentStyle.justifyContent = 'flex-end'; break; case 'middle': default: alignmentStyle.justifyContent = 'center'; break; } switch (location) { case 'top': alignmentStyle.alignItems = 'flex-start'; break; case 'bottom': alignmentStyle.alignItems = 'flex-end'; break; case 'middle': default: alignmentStyle.alignItems = 'center'; break; } return alignmentStyle; }; const dimensions = { width: direction === 'horizontal' ? 0 : paginationHeight, height: direction === 'horizontal' ? paginationHeight : 0, }; const defaultContent = (<ScrollView horizontal={direction === 'horizontal'} contentContainerStyle={[ styles.paginationContainer, { backgroundColor }, getAlignmentStyle(), { flexDirection: direction === 'vertical' ? 'column' : 'row' }, ]} showsHorizontalScrollIndicator={false} showsVerticalScrollIndicator={false} style={{ display: showAspect ? 'flex' : 'none', padding: 0, margin: 0, width: direction === 'horizontal' ? '100%' : paginationHeight, height: direction === 'horizontal' ? paginationHeight : '100%', maxHeight: direction === 'horizontal' ? paginationHeight : '100%', maxWidth: direction === 'horizontal' ? '100%' : paginationHeight, ...style, }}> <View style={[ styles.paginationRail, { flexDirection: direction === 'vertical' ? 'column' : 'row' }, ]}> {data.map((item) => renderItem({ item }))} </View> </ScrollView>); const content = renderContent ? renderContent({ defaultContent, dimensions }) : defaultContent; return renderContainer ? renderContainer({ defaultContainer: content, dimensions }) : content; }; export default Pagination; /** * Stylesheet for the Pagination component. */ const styles = StyleSheet.create({ paginationContainer: { flexGrow: 1, padding: 0, margin: 0, alignItems: 'center', justifyContent: 'center', }, paginationRail: { alignItems: 'center', justifyContent: 'center', }, pageButton: { minWidth: 36, paddingVertical: 6, paddingHorizontal: 10, borderWidth: 1, borderColor: '#2c678f', borderRadius: 999, marginHorizontal: 4, marginVertical: 4, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', }, activePage: { backgroundColor: '#2c678f', borderColor: '#2c678f', }, inactivePage: { backgroundColor: '#ffffff', borderColor: '#2c678f', }, pageText: { color: '#000000', fontSize: 13, fontWeight: 'bold', shadowColor: '#000000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.05, shadowRadius: 1.2, }, lockContainer: { flexDirection: 'row', alignItems: 'center', gap: 4, }, lockIcon: { marginLeft: 2, color: '#000000', }, }); //# sourceMappingURL=Pagination.js.map