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