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