import React, {useMemo} from 'react'; import {StyleSheet, View, TouchableOpacity} from 'react-native'; import {SwipeableListItemProps, SwipeableListItemSwipeActionsProps, SwipeAction} from './types'; import {SwipeableListItemContext} from './SwipeableListItemContext'; import {SwipeableListItemAvatar} from './SwipeableListItemAvatar'; import {SwipeableListItemContent} from './SwipeableListItemContent'; import {SwipeableListItemMetadata} from './SwipeableListItemMetadata'; import { SwipeableListItemSwipeActions, buildLeftSwipeActions, buildRightSwipeActions, } from './SwipeableListItemSwipeActions'; import {SwipeableListItemSelection} from './SwipeableListItemSelection'; import {SwipeableListItemSelected} from './SwipeableListItemSelected'; import {SwipeableListItemUnselected} from './SwipeableListItemUnselected'; import {SwipeableListItemSwipeLeftActions} from './SwipeableListItemSwipeLeftActions'; import {SwipeableListItemSwipeRightActions} from './SwipeableListItemSwipeRightActions'; import {SwipeableListItemSwipeLeftAction} from './SwipeableListItemSwipeLeftAction'; import {SwipeableListItemSwipeRightAction} from './SwipeableListItemSwipeRightAction'; import {SwipeableListItemTitle} from './SwipeableListItemTitle'; import {SwipeableListItemSubTitle} from './SwipeableListItemSubTitle'; import {SwipeableListItemIcon} from './SwipeableListItemIcon'; import SwipeableComponent from '../Swipeable/Swipeable'; import {useTheme, ColorsScheme} from '../../theme/ThemeContext'; /** * SwipeableListItem - A flexible compound component for creating swipeable list items * * @example * ```tsx * // New declarative swipe actions * {}}> * * {}}> * Read * * * * * {}}> * Delete * * * * * * John Doe * Hey there! * * * 📌 * * * ``` */ type SwipeableListItemComponent = React.FC & { Avatar: typeof SwipeableListItemAvatar; Content: typeof SwipeableListItemContent; Metadata: typeof SwipeableListItemMetadata; // Legacy SwipeActions: typeof SwipeableListItemSwipeActions; // New declarative API SwipeLeftActions: typeof SwipeableListItemSwipeLeftActions; SwipeRightActions: typeof SwipeableListItemSwipeRightActions; SwipeLeftAction: typeof SwipeableListItemSwipeLeftAction; SwipeRightAction: typeof SwipeableListItemSwipeRightAction; // Content sub-components Title: typeof SwipeableListItemTitle; SubTitle: typeof SwipeableListItemSubTitle; // Metadata sub-components Icon: typeof SwipeableListItemIcon; // Selection compound component Selection: typeof SwipeableListItemSelection; Selected: typeof SwipeableListItemSelected; Unselected: typeof SwipeableListItemUnselected; }; const SwipeableListItem: SwipeableListItemComponent = ({ children, containerStyle, backgroundColor, selected, onSelectChange, isSelection, onPress, onLongPress, ...touchableProps // Capture all remaining TouchableOpacity props }) => { const {colors} = useTheme(); const styles = themedStyle(colors); // Extract new declarative swipe actions from children const {leftActions, rightActions, swipeConfig} = useMemo(() => { let config: SwipeableListItemSwipeActionsProps | undefined; const extractedLeftActions: SwipeAction[] = []; const extractedRightActions: SwipeAction[] = []; React.Children.forEach(children, child => { if (!React.isValidElement(child)) return; // Legacy SwipeActions component if (child.type === SwipeableListItemSwipeActions) { config = child.props as SwipeableListItemSwipeActionsProps; } // New declarative SwipeLeftActions if (child.type === SwipeableListItemSwipeLeftActions) { const props = child.props as any; if (props.children) { React.Children.forEach(props.children, actionChild => { if (React.isValidElement(actionChild) && actionChild.type === SwipeableListItemSwipeLeftAction) { const actionProps = actionChild.props as any; // Extract children and TouchableOpacity props const {children: actionChildren, backgroundColor, width, onPress, style, ...touchableProps} = actionProps; extractedLeftActions.push({ comp: actionChildren, color: backgroundColor, width: width || 80, onPress, touchableProps, // Pass through all TouchableOpacity props }); } }); } } // New declarative SwipeRightActions if (child.type === SwipeableListItemSwipeRightActions) { const props = child.props as any; if (props.children) { React.Children.forEach(props.children, actionChild => { if (React.isValidElement(actionChild) && actionChild.type === SwipeableListItemSwipeRightAction) { const actionProps = actionChild.props as any; // Extract children and TouchableOpacity props const {children: actionChildren, backgroundColor, width, onPress, style, ...touchableProps} = actionProps; extractedRightActions.push({ comp: actionChildren, color: backgroundColor, width: width || 80, onPress, touchableProps, // Pass through all TouchableOpacity props }); } }); } } }); // Use extracted declarative actions if provided // Otherwise, fallback to legacy config (only if config exists) const finalLeftActions = extractedLeftActions.length > 0 ? extractedLeftActions : (config ? buildLeftSwipeActions(config, colors) : []); const finalRightActions = extractedRightActions.length > 0 ? extractedRightActions : (config ? buildRightSwipeActions(config, colors) : []); return { leftActions: finalLeftActions, rightActions: finalRightActions, swipeConfig: config, }; }, [children, colors]); // Context value for child components const contextValue = useMemo( () => ({ backgroundColor, onPress, onLongPress, selected, onSelectChange, isSelection, swipeConfig, }), [backgroundColor, onPress, onLongPress, selected, onSelectChange, isSelection, swipeConfig] ); // Determine if swipe is enabled (disabled in selection mode) const leftSwipeEnabled = !isSelection && leftActions.length > 0; const rightSwipeEnabled = !isSelection && rightActions.length > 0; // Filter out configuration-only components from render const renderChildren = React.Children.toArray(children).filter( child => !React.isValidElement(child) || ( child.type !== SwipeableListItemSwipeActions && child.type !== SwipeableListItemSwipeLeftActions && child.type !== SwipeableListItemSwipeRightActions ) ); // Render content const renderContent = () => ( {renderChildren} ); // Wrap with swipeable if actions are configured if (leftSwipeEnabled || rightSwipeEnabled) { return ( {renderContent()} ); } return ( {renderContent()} ); }; // Attach sub-components SwipeableListItem.Avatar = SwipeableListItemAvatar; SwipeableListItem.Content = SwipeableListItemContent; SwipeableListItem.Metadata = SwipeableListItemMetadata; // Legacy swipe actions SwipeableListItem.SwipeActions = SwipeableListItemSwipeActions; // New declarative swipe actions API SwipeableListItem.SwipeLeftActions = SwipeableListItemSwipeLeftActions; SwipeableListItem.SwipeRightActions = SwipeableListItemSwipeRightActions; SwipeableListItem.SwipeLeftAction = SwipeableListItemSwipeLeftAction; SwipeableListItem.SwipeRightAction = SwipeableListItemSwipeRightAction; // Content sub-components SwipeableListItem.Title = SwipeableListItemTitle; SwipeableListItem.SubTitle = SwipeableListItemSubTitle; // Metadata sub-components SwipeableListItem.Icon = SwipeableListItemIcon; // Selection compound component SwipeableListItem.Selection = SwipeableListItemSelection; SwipeableListItem.Selected = SwipeableListItemSelected; SwipeableListItem.Unselected = SwipeableListItemUnselected; const themedStyle = (colors: ColorsScheme) => StyleSheet.create({ container: { backgroundColor: colors.background_primary, flexDirection: 'row', alignItems: 'flex-start', paddingLeft: 16, paddingTop: 10, height: 77.33, paddingRight: 31, width: '100%', }, bottomSeparator: { position: 'absolute', bottom: 0.1, right: 0, width: '88%', justifyContent: 'center', borderBottomWidth: 0.3, borderBottomColor: colors.separator_non_opaque, }, }); export default SwipeableListItem;