diff --git a/Libraries/Experimental/SwipeableRow/SwipeableFlatList.js b/Libraries/Experimental/SwipeableRow/SwipeableFlatList.js deleted file mode 100644 index 5bbe37f0a0..0000000000 --- a/Libraries/Experimental/SwipeableRow/SwipeableFlatList.js +++ /dev/null @@ -1,172 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - * @format - */ -'use strict'; - -import type {Props as FlatListProps} from 'FlatList'; -import type {renderItemType} from 'VirtualizedList'; - -const React = require('React'); -const SwipeableRow = require('SwipeableRow'); -const FlatList = require('FlatList'); - -// TODO: Make this $ReadOnly and Exact. Will require doing the same to the props in -// Libraries/Lists/* -type SwipableListProps = { - /** - * To alert the user that swiping is possible, the first row can bounce - * on component mount. - */ - bounceFirstRowOnMount: boolean, - - /** - * Maximum distance to open to after a swipe - */ - maxSwipeDistance: number | (Object => number), - - /** - * Callback method to render the view that will be unveiled on swipe - */ - renderQuickActions: renderItemType, -}; - -type Props = SwipableListProps & FlatListProps; - -type State = {| - openRowKey: ?string, -|}; - -/** - * A container component that renders multiple SwipeableRow's in a FlatList - * implementation. This is designed to be a drop-in replacement for the - * standard React Native `FlatList`, so use it as if it were a FlatList, but - * with extra props. - * - * SwipeableRow can be used independently of this component, but the main - * benefits of using this component are: - * - * - It ensures that at most 1 row is swiped open (auto closes others) - * - It can bounce the 1st row of the list so users know it's swipeable - * - Increase performance on iOS by locking list swiping when row swiping is occurring - * - More to come - */ - -class SwipeableFlatList extends React.Component, State> { - _flatListRef: ?FlatList = null; - _shouldBounceFirstRowOnMount: boolean = false; - - static defaultProps = { - ...FlatList.defaultProps, - bounceFirstRowOnMount: true, - renderQuickActions: () => null, - }; - - constructor(props: Props, context: any): void { - super(props, context); - this.state = { - openRowKey: null, - }; - - this._shouldBounceFirstRowOnMount = this.props.bounceFirstRowOnMount; - } - - render(): React.Node { - return ( - { - this._flatListRef = ref; - }} - onScroll={this._onScroll} - renderItem={this._renderItem} - extraData={this.state} - /> - ); - } - - _onScroll = (e): void => { - // Close any opens rows on ListView scroll - if (this.state.openRowKey) { - this.setState({ - openRowKey: null, - }); - } - - this.props.onScroll && this.props.onScroll(e); - }; - - _renderItem = (info: Object): ?React.Element => { - const slideoutView = this.props.renderQuickActions(info); - const key = this.props.keyExtractor(info.item, info.index); - - // If renderQuickActions is unspecified or returns falsey, don't allow swipe - if (!slideoutView) { - return this.props.renderItem(info); - } - - let shouldBounceOnMount = false; - if (this._shouldBounceFirstRowOnMount) { - this._shouldBounceFirstRowOnMount = false; - shouldBounceOnMount = true; - } - - return ( - this._onOpen(key)} - onClose={() => this._onClose(key)} - shouldBounceOnMount={shouldBounceOnMount} - onSwipeEnd={this._setListViewScrollable} - onSwipeStart={this._setListViewNotScrollable}> - {this.props.renderItem(info)} - - ); - }; - - // This enables rows having variable width slideoutView. - _getMaxSwipeDistance(info: Object): number { - if (typeof this.props.maxSwipeDistance === 'function') { - return this.props.maxSwipeDistance(info); - } - - return this.props.maxSwipeDistance; - } - - _setListViewScrollableTo(value: boolean) { - if (this._flatListRef) { - this._flatListRef.setNativeProps({ - scrollEnabled: value, - }); - } - } - - _setListViewScrollable = () => { - this._setListViewScrollableTo(true); - }; - - _setListViewNotScrollable = () => { - this._setListViewScrollableTo(false); - }; - - _onOpen(key: any): void { - this.setState({ - openRowKey: key, - }); - } - - _onClose(key: any): void { - this.setState({ - openRowKey: null, - }); - } -} - -module.exports = SwipeableFlatList; diff --git a/Libraries/Experimental/SwipeableRow/SwipeableQuickActionButton.js b/Libraries/Experimental/SwipeableRow/SwipeableQuickActionButton.js deleted file mode 100644 index 0ad96e4d22..0000000000 --- a/Libraries/Experimental/SwipeableRow/SwipeableQuickActionButton.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - * @flow - */ - -'use strict'; - -const DeprecatedViewPropTypes = require('DeprecatedViewPropTypes'); -const Image = require('Image'); -const React = require('React'); -const Text = require('Text'); -const TouchableHighlight = require('TouchableHighlight'); -const View = require('View'); - -import type {ImageSource} from 'ImageSource'; - -/** - * Standard set of quick action buttons that can, if the user chooses, be used - * with SwipeableListView. Each button takes an image and text with optional - * formatting. - */ -class SwipeableQuickActionButton extends React.Component<{ - accessibilityLabel?: string, - imageSource?: ?(ImageSource | number), - /* $FlowFixMe(>=0.82.0 site=react_native_fb) This comment suppresses an error - * found when Flow v0.82 was deployed. To see the error delete this comment - * and run Flow. */ - imageStyle?: ?DeprecatedViewPropTypes.style, - mainView?: ?React.Node, - onPress?: Function, - /* $FlowFixMe(>=0.82.0 site=react_native_fb) This comment suppresses an error - * found when Flow v0.82 was deployed. To see the error delete this comment - * and run Flow. */ - style?: ?DeprecatedViewPropTypes.style, - /* $FlowFixMe(>=0.82.0 site=react_native_fb) This comment suppresses an error - * found when Flow v0.82 was deployed. To see the error delete this comment - * and run Flow. */ - containerStyle?: ?DeprecatedViewPropTypes.style, - testID?: string, - text?: ?(string | Object | Array), - /* $FlowFixMe(>=0.82.0 site=react_native_fb) This comment suppresses an error - * found when Flow v0.82 was deployed. To see the error delete this comment - * and run Flow. */ - textStyle?: ?DeprecatedViewPropTypes.style, -}> { - render(): React.Node { - if (!this.props.imageSource && !this.props.text && !this.props.mainView) { - return null; - } - const mainView = this.props.mainView ? ( - this.props.mainView - ) : ( - - - {this.props.text} - - ); - return ( - - {mainView} - - ); - } -} - -module.exports = SwipeableQuickActionButton; diff --git a/Libraries/Experimental/SwipeableRow/SwipeableQuickActions.js b/Libraries/Experimental/SwipeableRow/SwipeableQuickActions.js deleted file mode 100644 index c97f77648a..0000000000 --- a/Libraries/Experimental/SwipeableRow/SwipeableQuickActions.js +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - * @flow strict-local - */ - -'use strict'; - -const React = require('React'); -const StyleSheet = require('StyleSheet'); -const View = require('View'); - -import type {ViewStyleProp} from 'StyleSheet'; - -type Props = $ReadOnly<{| - style?: ?ViewStyleProp, - children: React.Node, -|}>; - -/** - * A thin wrapper around standard quick action buttons that can, if the user - * chooses, be used with SwipeableListView. Sample usage is as follows, in the - * renderQuickActions callback: - * - * - * - * - * - */ -class SwipeableQuickActions extends React.Component { - render(): React.Node { - const children = this.props.children; - let buttons = []; - - // Multiple children - if (children instanceof Array) { - for (let i = 0; i < children.length; i++) { - buttons.push(children[i]); - - if (i < children.length - 1) { - // Not last button - buttons.push(); - } - } - } else { - // 1 child - buttons = children; - } - - return {buttons}; - } -} - -const styles = StyleSheet.create({ - background: { - flex: 1, - flexDirection: 'row', - justifyContent: 'flex-end', - }, - divider: { - width: 4, - }, -}); - -module.exports = SwipeableQuickActions; diff --git a/Libraries/Experimental/SwipeableRow/SwipeableRow.js b/Libraries/Experimental/SwipeableRow/SwipeableRow.js deleted file mode 100644 index de707a3549..0000000000 --- a/Libraries/Experimental/SwipeableRow/SwipeableRow.js +++ /dev/null @@ -1,384 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - * @flow - */ - -'use strict'; - -const Animated = require('Animated'); -const I18nManager = require('I18nManager'); -const PanResponder = require('PanResponder'); -const React = require('React'); -const StyleSheet = require('StyleSheet'); -const View = require('View'); - -import type {LayoutEvent, PressEvent} from 'CoreEventTypes'; -import type {GestureState} from 'PanResponder'; - -const IS_RTL = I18nManager.isRTL; - -// NOTE: Eventually convert these consts to an input object of configurations - -// Position of the left of the swipable item when closed -const CLOSED_LEFT_POSITION = 0; -// Minimum swipe distance before we recognize it as such -const HORIZONTAL_SWIPE_DISTANCE_THRESHOLD = 10; -// Minimum swipe speed before we fully animate the user's action (open/close) -const HORIZONTAL_FULL_SWIPE_SPEED_THRESHOLD = 0.3; -// Factor to divide by to get slow speed; i.e. 4 means 1/4 of full speed -const SLOW_SPEED_SWIPE_FACTOR = 4; -// Time, in milliseconds, of how long the animated swipe should be -const SWIPE_DURATION = 300; - -/** - * On SwipeableListView mount, the 1st item will bounce to show users it's - * possible to swipe - */ -const ON_MOUNT_BOUNCE_DELAY = 700; -const ON_MOUNT_BOUNCE_DURATION = 400; - -// Distance left of closed position to bounce back when right-swiping from closed -const RIGHT_SWIPE_BOUNCE_BACK_DISTANCE = 30; -const RIGHT_SWIPE_BOUNCE_BACK_DURATION = 300; -/** - * Max distance of right swipe to allow (right swipes do functionally nothing). - * Must be multiplied by SLOW_SPEED_SWIPE_FACTOR because gestureState.dx tracks - * how far the finger swipes, and not the actual animation distance. - */ -const RIGHT_SWIPE_THRESHOLD = 30 * SLOW_SPEED_SWIPE_FACTOR; -const DEFAULT_SWIPE_THRESHOLD = 30; - -const emptyFunction = () => {}; - -type Props = $ReadOnly<{| - children?: ?React.Node, - isOpen?: ?boolean, - maxSwipeDistance?: ?number, - onClose?: ?() => void, - onOpen?: ?() => void, - onSwipeEnd?: ?() => void, - onSwipeStart?: ?() => void, - preventSwipeRight?: ?boolean, - shouldBounceOnMount?: ?boolean, - slideoutView?: ?React.Node, - swipeThreshold?: ?number, -|}>; - -type State = { - currentLeft: Animated.Value, - isSwipeableViewRendered: boolean, - rowHeight: ?number, -}; - -/** - * Creates a swipable row that allows taps on the main item and a custom View - * on the item hidden behind the row. Typically this should be used in - * conjunction with SwipeableListView for additional functionality, but can be - * used in a normal ListView. See the renderRow for SwipeableListView to see how - * to use this component separately. - */ -class SwipeableRow extends React.Component { - _handleMoveShouldSetPanResponderCapture = ( - event: PressEvent, - gestureState: GestureState, - ): boolean => { - // Decides whether a swipe is responded to by this component or its child - return gestureState.dy < 10 && this._isValidSwipe(gestureState); - }; - - _handlePanResponderGrant = ( - event: PressEvent, - gestureState: GestureState, - ): void => {}; - - _handlePanResponderMove = ( - event: PressEvent, - gestureState: GestureState, - ): void => { - if (this._isSwipingExcessivelyRightFromClosedPosition(gestureState)) { - return; - } - - this.props.onSwipeStart && this.props.onSwipeStart(); - - if (this._isSwipingRightFromClosed(gestureState)) { - this._swipeSlowSpeed(gestureState); - } else { - this._swipeFullSpeed(gestureState); - } - }; - - _onPanResponderTerminationRequest = ( - event: PressEvent, - gestureState: GestureState, - ): boolean => { - return false; - }; - - _handlePanResponderEnd = ( - event: PressEvent, - gestureState: GestureState, - ): void => { - const horizontalDistance = IS_RTL ? -gestureState.dx : gestureState.dx; - if (this._isSwipingRightFromClosed(gestureState)) { - this.props.onOpen && this.props.onOpen(); - this._animateBounceBack(RIGHT_SWIPE_BOUNCE_BACK_DURATION); - } else if (this._shouldAnimateRemainder(gestureState)) { - if (horizontalDistance < 0) { - // Swiped left - this.props.onOpen && this.props.onOpen(); - this._animateToOpenPositionWith(gestureState.vx, horizontalDistance); - } else { - // Swiped right - this.props.onClose && this.props.onClose(); - this._animateToClosedPosition(); - } - } else { - if (this._previousLeft === CLOSED_LEFT_POSITION) { - this._animateToClosedPosition(); - } else { - this._animateToOpenPosition(); - } - } - - this.props.onSwipeEnd && this.props.onSwipeEnd(); - }; - - _panResponder = PanResponder.create({ - onMoveShouldSetPanResponderCapture: this - ._handleMoveShouldSetPanResponderCapture, - onPanResponderGrant: this._handlePanResponderGrant, - onPanResponderMove: this._handlePanResponderMove, - onPanResponderRelease: this._handlePanResponderEnd, - onPanResponderTerminationRequest: this._onPanResponderTerminationRequest, - onPanResponderTerminate: this._handlePanResponderEnd, - onShouldBlockNativeResponder: (event, gestureState) => false, - }); - - _previousLeft = CLOSED_LEFT_POSITION; - _timeoutID: ?TimeoutID = null; - - state = { - currentLeft: new Animated.Value(this._previousLeft), - /** - * In order to render component A beneath component B, A must be rendered - * before B. However, this will cause "flickering", aka we see A briefly - * then B. To counter this, _isSwipeableViewRendered flag is used to set - * component A to be transparent until component B is loaded. - */ - isSwipeableViewRendered: false, - rowHeight: null, - }; - - componentDidMount(): void { - if (this.props.shouldBounceOnMount) { - /** - * Do the on mount bounce after a delay because if we animate when other - * components are loading, the animation will be laggy - */ - this._timeoutID = setTimeout(() => { - this._animateBounceBack(ON_MOUNT_BOUNCE_DURATION); - }, ON_MOUNT_BOUNCE_DELAY); - } - } - - UNSAFE_componentWillReceiveProps(nextProps: $Shape): void { - /** - * We do not need an "animateOpen(noCallback)" because this animation is - * handled internally by this component. - */ - const isOpen = this.props.isOpen ?? false; - const nextIsOpen = nextProps.isOpen ?? false; - - if (isOpen && !nextIsOpen) { - this._animateToClosedPosition(); - } - } - - componentWillUnmount() { - if (this._timeoutID != null) { - clearTimeout(this._timeoutID); - } - } - - render(): React.Element { - // The view hidden behind the main view - let slideOutView; - if (this.state.isSwipeableViewRendered && this.state.rowHeight) { - slideOutView = ( - - {this.props.slideoutView} - - ); - } - - // The swipeable item - const swipeableView = ( - - {this.props.children} - - ); - - return ( - - {slideOutView} - {swipeableView} - - ); - } - - close(): void { - this.props.onClose && this.props.onClose(); - this._animateToClosedPosition(); - } - - _onSwipeableViewLayout = (event: LayoutEvent): void => { - this.setState({ - isSwipeableViewRendered: true, - rowHeight: event.nativeEvent.layout.height, - }); - }; - - _isSwipingRightFromClosed(gestureState: GestureState): boolean { - const gestureStateDx = IS_RTL ? -gestureState.dx : gestureState.dx; - return this._previousLeft === CLOSED_LEFT_POSITION && gestureStateDx > 0; - } - - _swipeFullSpeed(gestureState: GestureState): void { - this.state.currentLeft.setValue(this._previousLeft + gestureState.dx); - } - - _swipeSlowSpeed(gestureState: GestureState): void { - this.state.currentLeft.setValue( - this._previousLeft + gestureState.dx / SLOW_SPEED_SWIPE_FACTOR, - ); - } - - _isSwipingExcessivelyRightFromClosedPosition( - gestureState: GestureState, - ): boolean { - /** - * We want to allow a BIT of right swipe, to allow users to know that - * swiping is available, but swiping right does not do anything - * functionally. - */ - const gestureStateDx = IS_RTL ? -gestureState.dx : gestureState.dx; - return ( - this._isSwipingRightFromClosed(gestureState) && - gestureStateDx > RIGHT_SWIPE_THRESHOLD - ); - } - - _animateTo( - toValue: number, - duration: number = SWIPE_DURATION, - callback: Function = emptyFunction, - ): void { - Animated.timing(this.state.currentLeft, { - duration, - toValue, - useNativeDriver: true, - }).start(() => { - this._previousLeft = toValue; - callback(); - }); - } - - _animateToOpenPosition(): void { - const maxSwipeDistance = this.props.maxSwipeDistance ?? 0; - const directionAwareMaxSwipeDistance = IS_RTL - ? -maxSwipeDistance - : maxSwipeDistance; - this._animateTo(-directionAwareMaxSwipeDistance); - } - - _animateToOpenPositionWith(speed: number, distMoved: number): void { - /** - * Ensure the speed is at least the set speed threshold to prevent a slow - * swiping animation - */ - speed = - speed > HORIZONTAL_FULL_SWIPE_SPEED_THRESHOLD - ? speed - : HORIZONTAL_FULL_SWIPE_SPEED_THRESHOLD; - const maxSwipeDistance = this.props.maxSwipeDistance ?? 0; - /** - * Calculate the duration the row should take to swipe the remaining distance - * at the same speed the user swiped (or the speed threshold) - */ - const duration = Math.abs((maxSwipeDistance - Math.abs(distMoved)) / speed); - const directionAwareMaxSwipeDistance = IS_RTL - ? -maxSwipeDistance - : maxSwipeDistance; - this._animateTo(-directionAwareMaxSwipeDistance, duration); - } - - _animateToClosedPosition(duration: number = SWIPE_DURATION): void { - this._animateTo(CLOSED_LEFT_POSITION, duration); - } - - _animateToClosedPositionDuringBounce = (): void => { - this._animateToClosedPosition(RIGHT_SWIPE_BOUNCE_BACK_DURATION); - }; - - _animateBounceBack(duration: number): void { - /** - * When swiping right, we want to bounce back past closed position on release - * so users know they should swipe right to get content. - */ - const swipeBounceBackDistance = IS_RTL - ? -RIGHT_SWIPE_BOUNCE_BACK_DISTANCE - : RIGHT_SWIPE_BOUNCE_BACK_DISTANCE; - this._animateTo( - -swipeBounceBackDistance, - duration, - this._animateToClosedPositionDuringBounce, - ); - } - - // Ignore swipes due to user's finger moving slightly when tapping - _isValidSwipe(gestureState: GestureState): boolean { - const preventSwipeRight = this.props.preventSwipeRight ?? false; - if ( - preventSwipeRight && - this._previousLeft === CLOSED_LEFT_POSITION && - gestureState.dx > 0 - ) { - return false; - } - - return Math.abs(gestureState.dx) > HORIZONTAL_SWIPE_DISTANCE_THRESHOLD; - } - - _shouldAnimateRemainder(gestureState: GestureState): boolean { - /** - * If user has swiped past a certain distance, animate the rest of the way - * if they let go - */ - const swipeThreshold = this.props.swipeThreshold ?? DEFAULT_SWIPE_THRESHOLD; - return ( - Math.abs(gestureState.dx) > swipeThreshold || - gestureState.vx > HORIZONTAL_FULL_SWIPE_SPEED_THRESHOLD - ); - } -} - -const styles = StyleSheet.create({ - slideOutContainer: { - bottom: 0, - left: 0, - position: 'absolute', - right: 0, - top: 0, - }, -}); - -module.exports = SwipeableRow; diff --git a/Libraries/react-native/react-native-implementation.js b/Libraries/react-native/react-native-implementation.js index 0fba5df8d6..deee26c0d5 100644 --- a/Libraries/react-native/react-native-implementation.js +++ b/Libraries/react-native/react-native-implementation.js @@ -137,9 +137,6 @@ module.exports = { ); return require('StatusBar'); }, - get SwipeableFlatList() { - return require('SwipeableFlatList'); - }, get Text() { return require('Text'); }, diff --git a/RNTester/js/RNTesterList.android.js b/RNTester/js/RNTesterList.android.js index d4bf68db97..9946ad0df6 100644 --- a/RNTester/js/RNTesterList.android.js +++ b/RNTester/js/RNTesterList.android.js @@ -71,10 +71,6 @@ const ComponentExamples: Array = [ key: 'StatusBarExample', module: require('./StatusBarExample'), }, - { - key: 'SwipeableFlatListExample', - module: require('./SwipeableFlatListExample'), - }, { key: 'SwitchExample', module: require('./SwitchExample'), diff --git a/RNTester/js/RNTesterList.ios.js b/RNTester/js/RNTesterList.ios.js index c5d6c2b012..0a5557727c 100644 --- a/RNTester/js/RNTesterList.ios.js +++ b/RNTester/js/RNTesterList.ios.js @@ -128,11 +128,6 @@ const ComponentExamples: Array = [ module: require('./StatusBarExample'), supportsTVOS: false, }, - { - key: 'SwipeableFlatListExample', - module: require('./SwipeableFlatListExample'), - supportsTVOS: false, - }, { key: 'SwitchExample', module: require('./SwitchExample'), diff --git a/RNTester/js/SwipeableFlatListExample.js b/RNTester/js/SwipeableFlatListExample.js deleted file mode 100644 index d79336cc30..0000000000 --- a/RNTester/js/SwipeableFlatListExample.js +++ /dev/null @@ -1,150 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - * @format - */ -'use strict'; - -const React = require('react'); -const ReactNative = require('react-native'); -const { - Image, - SwipeableFlatList, - TouchableHighlight, - StyleSheet, - Text, - View, - Alert, -} = ReactNative; - -const RNTesterPage = require('./RNTesterPage'); - -import type {RNTesterProps} from 'RNTesterTypes'; - -const data = [ - { - key: 'like', - icon: require('./Thumbnails/like.png'), - data: 'Like!', - }, - { - key: 'heart', - icon: require('./Thumbnails/heart.png'), - data: 'Heart!', - }, - { - key: 'party', - icon: require('./Thumbnails/party.png'), - data: 'Party!', - }, -]; - -class SwipeableFlatListExample extends React.Component { - render() { - return ( - '} - noSpacer={true} - noScroll={true}> - - - ); - } - - _renderItem({item}): ?React.Element { - return ( - - - - {item.data} - - - ); - } - - _renderQuickActions({item}: Object): ?React.Element { - return ( - - { - Alert.alert( - 'Tips', - 'You could do something with this edit action!', - ); - }}> - Edit - - { - Alert.alert( - 'Tips', - 'You could do something with this remove action!', - ); - }}> - Remove - - - ); - } -} - -const styles = StyleSheet.create({ - row: { - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center', - padding: 10, - backgroundColor: '#F6F6F6', - }, - rowIcon: { - width: 64, - height: 64, - marginRight: 20, - }, - rowData: { - flex: 1, - }, - rowDataText: { - fontSize: 24, - }, - actionsContainer: { - flex: 1, - flexDirection: 'row', - justifyContent: 'flex-end', - alignItems: 'center', - }, - actionButton: { - padding: 10, - width: 80, - backgroundColor: '#999999', - }, - actionButtonDestructive: { - backgroundColor: '#FF0000', - }, - actionButtonText: { - textAlign: 'center', - }, -}); - -exports.title = ''; -exports.description = 'Performant, scrollable, swipeable list of data.'; -exports.examples = [ - { - title: 'Simple swipable list', - render: function(): React.Element { - return ; - }, - }, -];