|
1 | | -import React, { useContext } from 'react'; |
2 | | -import { Pressable, ScrollView } from 'react-native'; |
3 | | -import { useRouter, usePathname } from 'expo-router'; |
4 | 1 | import { Box } from '@/components/ui/box'; |
| 2 | +import { Grid, GridItem } from '@/components/ui/grid'; |
5 | 3 | import { Heading } from '@/components/ui/heading'; |
6 | | -import { VStack } from '@/components/ui/vstack'; |
7 | | -import { Text } from '@/components/ui/text'; |
8 | | -import { getAllComponents } from '@/utils/getComponents'; |
9 | 4 | import { HStack } from '@/components/ui/hstack'; |
| 5 | +import { ChevronRightIcon, Icon } from '@/components/ui/icon'; |
10 | 6 | import { Image } from '@/components/ui/image'; |
11 | | -import { ColorModeContext } from './_layout'; |
12 | | -import { ChevronRightIcon } from '@/components/ui/icon'; |
13 | | -import { Icon } from '@/components/ui/icon'; |
14 | | -import { Grid, GridItem } from '@/components/ui/grid'; |
| 7 | +import { Text } from '@/components/ui/text'; |
| 8 | +import { VStack } from '@/components/ui/vstack'; |
| 9 | +import { getAllComponents } from '@/utils/getComponents'; |
| 10 | +import { usePathname, useRouter } from 'expo-router'; |
| 11 | +import React, { useContext, useMemo } from 'react'; |
| 12 | +import { FlatList, Pressable } from 'react-native'; |
15 | 13 | import { SafeAreaView } from 'react-native-safe-area-context'; |
| 14 | +import { ColorModeContext } from './_layout'; |
16 | 15 |
|
17 | 16 | const components = getAllComponents(); |
18 | 17 | const ComponentCard = ({ component, onPress }: any) => { |
@@ -51,7 +50,7 @@ const ComponentCard = ({ component, onPress }: any) => { |
51 | 50 | const Header = () => { |
52 | 51 | const { colorMode }: any = useContext(ColorModeContext); |
53 | 52 | return ( |
54 | | - <HStack className="flex-1 bg-background-50 w-full mx-auto justify-between"> |
| 53 | + <HStack className="bg-background-50 w-full mx-auto justify-between"> |
55 | 54 | <VStack className="w-full md:max-w-[630px] lg:max-w-[400px] xl:max-w-[480px] mx-5 md:ml-8 mb-8 mt-10 lg:my-[44px] xl:ml-[80px] flex-1"> |
56 | 55 | <HStack |
57 | 56 | className="rounded-full bg-background-0 py-4 px-5 mb-7 md:mb-9 lg:mb-[80px] xl:mb-[132px] items-center native:max-w-[250px] w-fit" |
@@ -101,63 +100,76 @@ export default function ComponentList() { |
101 | 100 | const handleComponentPress = (componentPath: string) => { |
102 | 101 | // Use Expo Router's built-in navigation state check |
103 | 102 | const targetPath = `components/${componentPath}`; |
104 | | - |
| 103 | + |
105 | 104 | // Prevent navigation if we're already on the target path or if navigation is in progress |
106 | 105 | if (pathname.includes(targetPath)) { |
107 | 106 | return; |
108 | 107 | } |
109 | | - |
| 108 | + |
110 | 109 | // Use replace instead of push to prevent stack accumulation on rapid clicks |
111 | 110 | router.push(`/components/${componentPath}` as any); |
112 | 111 | }; |
113 | 112 |
|
114 | 113 | // Filter out bottomsheet components and components without paths |
115 | | - const filteredComponents = components.map(category => ({ |
116 | | - ...category, |
117 | | - components: category.components.filter(component => |
118 | | - component.path && |
119 | | - !component.name.toLowerCase().includes('bottomsheet') && |
120 | | - !component.path.toLowerCase().includes('bottomsheet') |
121 | | - ) |
122 | | - })).filter(category => category.components.length > 0); |
| 114 | + const filteredComponents = useMemo( |
| 115 | + () => |
| 116 | + components |
| 117 | + .map((category) => ({ |
| 118 | + ...category, |
| 119 | + components: category.components.filter( |
| 120 | + (component) => |
| 121 | + component.path && |
| 122 | + !component.name.toLowerCase().includes('bottomsheet') && |
| 123 | + !component.path.toLowerCase().includes('bottomsheet') |
| 124 | + ), |
| 125 | + })) |
| 126 | + .filter((category) => category.components.length > 0), |
| 127 | + [] |
| 128 | + ); |
| 129 | + |
| 130 | + const renderCategoryItem = ({ item: category }: any) => ( |
| 131 | + <Box className="mt-4 border-b border-outline-100 pb-8 px-5 md:px-20"> |
| 132 | + <Heading size="lg" className="text-typography-900 mb-4"> |
| 133 | + {category.category} |
| 134 | + </Heading> |
| 135 | + <Grid |
| 136 | + className="gap-5" |
| 137 | + _extra={{ |
| 138 | + className: 'grid-cols-2 md:grid-cols-4 xl:grid-cols-6', |
| 139 | + }} |
| 140 | + > |
| 141 | + {category.components.map((component: any) => ( |
| 142 | + <GridItem |
| 143 | + _extra={{ |
| 144 | + className: 'col-span-1', |
| 145 | + }} |
| 146 | + key={component.name} |
| 147 | + > |
| 148 | + <ComponentCard |
| 149 | + component={component} |
| 150 | + onPress={() => handleComponentPress(component.path!)} |
| 151 | + /> |
| 152 | + </GridItem> |
| 153 | + ))} |
| 154 | + </Grid> |
| 155 | + </Box> |
| 156 | + ); |
123 | 157 |
|
124 | 158 | return ( |
125 | 159 | <SafeAreaView className="flex-1 bg-background-0"> |
126 | | - <ScrollView className="flex-1" showsVerticalScrollIndicator={false}> |
127 | | - <Header /> |
128 | | - <VStack className="p-5 md:px-20"> |
129 | | - {filteredComponents.map((category) => ( |
130 | | - <Box |
131 | | - key={category.category} |
132 | | - className="mt-4 border-b border-outline-100 pb-8" |
133 | | - > |
134 | | - <Heading size="lg" className="text-typography-900 mb-4"> |
135 | | - {category.category} |
136 | | - </Heading> |
137 | | - <Grid |
138 | | - className="gap-5" |
139 | | - _extra={{ |
140 | | - className: 'grid-cols-2 md:grid-cols-4 xl:grid-cols-6', |
141 | | - }} |
142 | | - > |
143 | | - {category.components.map((component) => ( |
144 | | - <GridItem |
145 | | - _extra={{ |
146 | | - className: 'col-span-1', |
147 | | - }} |
148 | | - key={component.name} |
149 | | - > |
150 | | - <ComponentCard |
151 | | - component={component} |
152 | | - onPress={() => handleComponentPress(component.path!)} |
153 | | - /> |
154 | | - </GridItem> |
155 | | - ))} |
156 | | - </Grid> |
157 | | - </Box> |
158 | | - ))} |
159 | | - </VStack> |
160 | | - </ScrollView> |
| 160 | + <FlatList |
| 161 | + data={filteredComponents} |
| 162 | + renderItem={renderCategoryItem} |
| 163 | + ListHeaderComponent={<Header />} |
| 164 | + keyExtractor={(item) => item.category} |
| 165 | + contentContainerStyle={{ paddingVertical: 20 }} |
| 166 | + showsVerticalScrollIndicator={false} |
| 167 | + removeClippedSubviews={true} |
| 168 | + maxToRenderPerBatch={3} |
| 169 | + updateCellsBatchingPeriod={50} |
| 170 | + initialNumToRender={2} |
| 171 | + windowSize={5} |
| 172 | + /> |
161 | 173 | </SafeAreaView> |
162 | 174 | ); |
163 | 175 | } |
0 commit comments