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