1import Ionicons from '@expo/vector-icons/build/Ionicons'; 2import { Link, useLinkProps } from '@react-navigation/native'; 3import React from 'react'; 4import { 5 FlatList, 6 ListRenderItem, 7 PixelRatio, 8 StatusBar, 9 StyleSheet, 10 Text, 11 TouchableHighlight, 12 View, 13 Platform, 14 Pressable, 15 useWindowDimensions, 16} from 'react-native'; 17import { useSafeAreaInsets } from 'react-native-safe-area-context'; 18 19export interface ListElement { 20 name: string; 21 route?: string; 22 isAvailable?: boolean; 23} 24 25interface Props { 26 apis: ListElement[]; 27 renderItemRight?: (props: ListElement) => React.ReactNode; 28 sort?: boolean; 29} 30 31function LinkButton({ 32 to, 33 action, 34 children, 35 ...rest 36}: React.ComponentProps<typeof Link> & { disabled?: boolean; children?: React.ReactNode }) { 37 const { onPress, ...props } = useLinkProps({ to, action }); 38 39 const [isPressed, setIsPressed] = React.useState(false); 40 41 if (Platform.OS === 'web') { 42 // It's important to use a `View` or `Text` on web instead of `TouchableX` 43 // Otherwise React Native for Web omits the `onClick` prop that's passed 44 // You'll also need to pass `onPress` as `onClick` to the `View` 45 // You can add hover effects using `onMouseEnter` and `onMouseLeave` 46 return ( 47 <Pressable 48 pointerEvents={rest.disabled === true ? 'none' : 'auto'} 49 onPressIn={() => setIsPressed(true)} 50 onPressOut={() => setIsPressed(false)} 51 onPress={onPress} 52 {...props} 53 {...rest} 54 style={[ 55 { 56 backgroundColor: isPressed ? '#dddddd' : undefined, 57 }, 58 rest.style, 59 ]}> 60 {children} 61 </Pressable> 62 ); 63 } 64 65 return ( 66 <TouchableHighlight underlayColor="#dddddd" onPress={onPress} {...props} {...rest}> 67 {children} 68 </TouchableHighlight> 69 ); 70} 71 72export default function ComponentListScreen(props: Props) { 73 React.useEffect(() => { 74 StatusBar.setHidden(false); 75 }, []); 76 77 const { width } = useWindowDimensions(); 78 const isMobile = width <= 640; 79 80 // adjust the right padding for safe area -- we don't need the left because that's where the drawer is. 81 const { bottom, right } = useSafeAreaInsets(); 82 83 const renderExampleSection: ListRenderItem<ListElement> = ({ item }) => { 84 const { route, name: exampleName, isAvailable } = item; 85 return ( 86 <LinkButton disabled={!isAvailable} to={route ?? exampleName} style={[styles.rowTouchable]}> 87 <View 88 pointerEvents="none" 89 style={[styles.row, !isAvailable && styles.disabledRow, { paddingRight: 10 + right }]}> 90 {props.renderItemRight && props.renderItemRight(item)} 91 <Text style={styles.rowLabel}>{exampleName}</Text> 92 <Text style={styles.rowDecorator}> 93 <Ionicons name="chevron-forward" size={18} color="#595959" /> 94 </Text> 95 </View> 96 </LinkButton> 97 ); 98 }; 99 100 const keyExtractor = React.useCallback((item: ListElement) => item.name, []); 101 102 const sortedApis = React.useMemo(() => { 103 if (props.sort === false) { 104 return props.apis; 105 } 106 return props.apis.sort((a, b) => { 107 if (a.isAvailable !== b.isAvailable) { 108 if (a.isAvailable) { 109 return -1; 110 } 111 return 1; 112 } 113 return a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1; 114 }); 115 }, [props.apis]); 116 117 return ( 118 <FlatList<ListElement> 119 initialNumToRender={25} 120 removeClippedSubviews={false} 121 keyboardShouldPersistTaps="handled" 122 keyboardDismissMode="on-drag" 123 contentContainerStyle={{ backgroundColor: '#fff', paddingBottom: isMobile ? 0 : bottom }} 124 data={sortedApis} 125 keyExtractor={keyExtractor} 126 renderItem={renderExampleSection} 127 /> 128 ); 129} 130 131const styles = StyleSheet.create({ 132 container: { 133 flex: 1, 134 paddingTop: 100, 135 }, 136 row: { 137 paddingHorizontal: 10, 138 paddingVertical: 14, 139 flexDirection: 'row', 140 justifyContent: 'space-between', 141 alignItems: 'center', 142 }, 143 rowDecorator: { 144 alignSelf: 'flex-end', 145 paddingRight: 4, 146 }, 147 rowTouchable: { 148 borderBottomWidth: 1.0 / PixelRatio.get(), 149 borderBottomColor: '#dddddd', 150 }, 151 disabledRow: { 152 opacity: 0.3, 153 }, 154 rowLabel: { 155 flex: 1, 156 fontSize: 15, 157 }, 158 rowIcon: { 159 marginRight: 10, 160 marginLeft: 6, 161 }, 162}); 163