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;