@legendapp/list 2.0.0-next.0 → 2.0.0-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/.claude/settings.local.json +8 -0
  2. package/.cursor/rules/changelog.mdc +60 -0
  3. package/.github/FUNDING.yml +15 -0
  4. package/.gitignore +5 -0
  5. package/.prettierrc.json +5 -0
  6. package/.vscode/settings.json +14 -0
  7. package/CLAUDE.md +126 -0
  8. package/biome.json +46 -0
  9. package/bun.lock +1289 -0
  10. package/bunfig.toml +2 -0
  11. package/dist/CHANGELOG.md +119 -0
  12. package/dist/LICENSE +21 -0
  13. package/dist/README.md +139 -0
  14. package/{animated.d.mts → dist/animated.d.mts} +1 -1
  15. package/{animated.d.ts → dist/animated.d.ts} +1 -1
  16. package/{index.d.mts → dist/index.d.mts} +63 -15
  17. package/{index.d.ts → dist/index.d.ts} +63 -15
  18. package/dist/index.js +2525 -0
  19. package/dist/index.mjs +2497 -0
  20. package/{keyboard-controller.d.mts → dist/keyboard-controller.d.mts} +4 -4
  21. package/{keyboard-controller.d.ts → dist/keyboard-controller.d.ts} +4 -4
  22. package/dist/package.json +35 -0
  23. package/example/README.md +40 -0
  24. package/example/api/data/genres.json +23 -0
  25. package/example/api/data/playlist/10402-10749.json +1 -0
  26. package/example/api/data/playlist/10402-10770.json +1 -0
  27. package/example/api/data/playlist/10402-37.json +1 -0
  28. package/example/api/data/playlist/10749-10752.json +1 -0
  29. package/example/api/data/playlist/10749-10770.json +1 -0
  30. package/example/api/data/playlist/10749-37.json +1 -0
  31. package/example/api/data/playlist/10749-878.json +1 -0
  32. package/example/api/data/playlist/10751-10402.json +1 -0
  33. package/example/api/data/playlist/10751-10752.json +1 -0
  34. package/example/api/data/playlist/10751-37.json +1 -0
  35. package/example/api/data/playlist/10751-53.json +1 -0
  36. package/example/api/data/playlist/10751-878.json +1 -0
  37. package/example/api/data/playlist/10751-9648.json +1 -0
  38. package/example/api/data/playlist/10752-37.json +1 -0
  39. package/example/api/data/playlist/12-10402.json +1 -0
  40. package/example/api/data/playlist/12-10749.json +1 -0
  41. package/example/api/data/playlist/12-18.json +1 -0
  42. package/example/api/data/playlist/12-27.json +1 -0
  43. package/example/api/data/playlist/12-35.json +1 -0
  44. package/example/api/data/playlist/14-36.json +1 -0
  45. package/example/api/data/playlist/14-878.json +1 -0
  46. package/example/api/data/playlist/16-10751.json +1 -0
  47. package/example/api/data/playlist/16-10770.json +1 -0
  48. package/example/api/data/playlist/16-35.json +1 -0
  49. package/example/api/data/playlist/16-36.json +1 -0
  50. package/example/api/data/playlist/16-53.json +1 -0
  51. package/example/api/data/playlist/18-10751.json +1 -0
  52. package/example/api/data/playlist/18-10752.json +1 -0
  53. package/example/api/data/playlist/18-37.json +1 -0
  54. package/example/api/data/playlist/18-53.json +1 -0
  55. package/example/api/data/playlist/18-878.json +1 -0
  56. package/example/api/data/playlist/27-10749.json +1 -0
  57. package/example/api/data/playlist/27-10770.json +1 -0
  58. package/example/api/data/playlist/28-10749.json +1 -0
  59. package/example/api/data/playlist/28-10751.json +1 -0
  60. package/example/api/data/playlist/28-10770.json +1 -0
  61. package/example/api/data/playlist/28-16.json +1 -0
  62. package/example/api/data/playlist/28-18.json +1 -0
  63. package/example/api/data/playlist/28-36.json +1 -0
  64. package/example/api/data/playlist/28-37.json +1 -0
  65. package/example/api/data/playlist/28-53.json +1 -0
  66. package/example/api/data/playlist/28-80.json +1 -0
  67. package/example/api/data/playlist/28-99.json +1 -0
  68. package/example/api/data/playlist/35-10749.json +1 -0
  69. package/example/api/data/playlist/35-10751.json +1 -0
  70. package/example/api/data/playlist/35-10752.json +1 -0
  71. package/example/api/data/playlist/35-27.json +1 -0
  72. package/example/api/data/playlist/35-36.json +1 -0
  73. package/example/api/data/playlist/35-53.json +1 -0
  74. package/example/api/data/playlist/35-80.json +1 -0
  75. package/example/api/data/playlist/36-37.json +1 -0
  76. package/example/api/data/playlist/36-878.json +1 -0
  77. package/example/api/data/playlist/36-9648.json +1 -0
  78. package/example/api/data/playlist/53-10752.json +1 -0
  79. package/example/api/data/playlist/80-10770.json +1 -0
  80. package/example/api/data/playlist/80-14.json +1 -0
  81. package/example/api/data/playlist/80-18.json +1 -0
  82. package/example/api/data/playlist/80-37.json +1 -0
  83. package/example/api/data/playlist/878-37.json +1 -0
  84. package/example/api/data/playlist/9648-10770.json +1 -0
  85. package/example/api/data/playlist/9648-37.json +1 -0
  86. package/example/api/data/playlist/9648-53.json +1 -0
  87. package/example/api/data/playlist/9648-878.json +1 -0
  88. package/example/api/data/playlist/99-10749.json +1 -0
  89. package/example/api/data/playlist/99-14.json +1 -0
  90. package/example/api/data/playlist/99-18.json +1 -0
  91. package/example/api/data/playlist/99-27.json +1 -0
  92. package/example/api/data/playlist/99-53.json +1 -0
  93. package/example/api/data/playlist/99-9648.json +1 -0
  94. package/example/api/data/playlist/index.ts +73 -0
  95. package/example/api/data/rows.json +1 -0
  96. package/example/api/index.ts +36 -0
  97. package/example/app/(tabs)/_layout.tsx +60 -0
  98. package/example/app/(tabs)/cards.tsx +81 -0
  99. package/example/app/(tabs)/index.tsx +205 -0
  100. package/example/app/(tabs)/moviesL.tsx +7 -0
  101. package/example/app/(tabs)/moviesLR.tsx +7 -0
  102. package/example/app/+not-found.tsx +32 -0
  103. package/example/app/_layout.tsx +34 -0
  104. package/example/app/accurate-scrollto/index.tsx +125 -0
  105. package/example/app/accurate-scrollto-2/index.tsx +52 -0
  106. package/example/app/accurate-scrollto-huge/index.tsx +128 -0
  107. package/example/app/add-to-end/index.tsx +82 -0
  108. package/example/app/ai-chat/index.tsx +236 -0
  109. package/example/app/bidirectional-infinite-list/index.tsx +133 -0
  110. package/example/app/cards-columns/index.tsx +37 -0
  111. package/example/app/cards-flashlist/index.tsx +122 -0
  112. package/example/app/cards-flatlist/index.tsx +94 -0
  113. package/example/app/cards-no-recycle/index.tsx +110 -0
  114. package/example/app/cards-renderItem.tsx +354 -0
  115. package/example/app/chat-example/index.tsx +167 -0
  116. package/example/app/chat-infinite/index.tsx +239 -0
  117. package/example/app/chat-keyboard/index.tsx +248 -0
  118. package/example/app/chat-resize-outer/index.tsx +247 -0
  119. package/example/app/columns/index.tsx +78 -0
  120. package/example/app/countries/index.tsx +182 -0
  121. package/example/app/countries-flashlist/index.tsx +163 -0
  122. package/example/app/countries-reorder/index.tsx +187 -0
  123. package/example/app/extra-data/index.tsx +86 -0
  124. package/example/app/filter-elements/filter-data-provider.tsx +55 -0
  125. package/example/app/filter-elements/index.tsx +118 -0
  126. package/example/app/initial-scroll-index/index.tsx +106 -0
  127. package/example/app/initial-scroll-index/renderFixedItem.tsx +215 -0
  128. package/example/app/initial-scroll-index-free-height/index.tsx +70 -0
  129. package/example/app/initial-scroll-index-keyed/index.tsx +62 -0
  130. package/example/app/lazy-list/index.tsx +123 -0
  131. package/example/app/movies-flashlist/index.tsx +7 -0
  132. package/example/app/mutable-cells/index.tsx +104 -0
  133. package/example/app/video-feed/index.tsx +119 -0
  134. package/example/app.config.js +22 -0
  135. package/example/app.json +45 -0
  136. package/example/assets/fonts/SpaceMono-Regular.ttf +0 -0
  137. package/example/assets/images/adaptive-icon.png +0 -0
  138. package/example/assets/images/favicon.png +0 -0
  139. package/example/assets/images/icon.png +0 -0
  140. package/example/assets/images/partial-react-logo.png +0 -0
  141. package/example/assets/images/react-logo.png +0 -0
  142. package/example/assets/images/react-logo@2x.png +0 -0
  143. package/example/assets/images/react-logo@3x.png +0 -0
  144. package/example/assets/images/splash-icon.png +0 -0
  145. package/example/autoscroll.sh +101 -0
  146. package/example/bun.lock +2266 -0
  147. package/example/bunfig.toml +2 -0
  148. package/example/components/Breathe.tsx +54 -0
  149. package/example/components/Circle.tsx +69 -0
  150. package/example/components/Collapsible.tsx +44 -0
  151. package/example/components/ExternalLink.tsx +24 -0
  152. package/example/components/HapticTab.tsx +18 -0
  153. package/example/components/HelloWave.tsx +37 -0
  154. package/example/components/Movies.tsx +179 -0
  155. package/example/components/ParallaxScrollView.tsx +81 -0
  156. package/example/components/ThemedText.tsx +60 -0
  157. package/example/components/ThemedView.tsx +14 -0
  158. package/example/components/__tests__/ThemedText-test.tsx +10 -0
  159. package/example/components/__tests__/__snapshots__/ThemedText-test.tsx.snap +24 -0
  160. package/example/components/ui/IconSymbol.ios.tsx +32 -0
  161. package/example/components/ui/IconSymbol.tsx +43 -0
  162. package/example/components/ui/TabBarBackground.ios.tsx +22 -0
  163. package/example/components/ui/TabBarBackground.tsx +6 -0
  164. package/example/constants/Colors.ts +26 -0
  165. package/example/constants/constants.ts +5 -0
  166. package/example/constants/useScrollTest.ts +19 -0
  167. package/example/hooks/useColorScheme.ts +1 -0
  168. package/example/hooks/useColorScheme.web.ts +8 -0
  169. package/example/hooks/useThemeColor.ts +22 -0
  170. package/example/ios/.xcode.env +11 -0
  171. package/example/ios/Podfile +64 -0
  172. package/example/ios/Podfile.lock +2767 -0
  173. package/example/ios/Podfile.properties.json +5 -0
  174. package/example/ios/listtest/AppDelegate.swift +70 -0
  175. package/example/ios/listtest/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png +0 -0
  176. package/example/ios/listtest/Images.xcassets/AppIcon.appiconset/Contents.json +14 -0
  177. package/example/ios/listtest/Images.xcassets/Contents.json +6 -0
  178. package/example/ios/listtest/Images.xcassets/SplashScreenBackground.colorset/Contents.json +20 -0
  179. package/example/ios/listtest/Images.xcassets/SplashScreenLogo.imageset/Contents.json +23 -0
  180. package/example/ios/listtest/Images.xcassets/SplashScreenLogo.imageset/image.png +0 -0
  181. package/example/ios/listtest/Images.xcassets/SplashScreenLogo.imageset/image@2x.png +0 -0
  182. package/example/ios/listtest/Images.xcassets/SplashScreenLogo.imageset/image@3x.png +0 -0
  183. package/example/ios/listtest/Info.plist +85 -0
  184. package/example/ios/listtest/PrivacyInfo.xcprivacy +48 -0
  185. package/example/ios/listtest/SplashScreen.storyboard +42 -0
  186. package/example/ios/listtest/Supporting/Expo.plist +12 -0
  187. package/example/ios/listtest/listtest-Bridging-Header.h +3 -0
  188. package/example/ios/listtest/listtest.entitlements +5 -0
  189. package/example/ios/listtest.xcodeproj/project.pbxproj +547 -0
  190. package/example/ios/listtest.xcodeproj/xcshareddata/xcschemes/listtest.xcscheme +88 -0
  191. package/example/ios/listtest.xcworkspace/contents.xcworkspacedata +10 -0
  192. package/example/metro.config.js +16 -0
  193. package/example/package.json +73 -0
  194. package/example/scripts/reset-project.js +84 -0
  195. package/example/tsconfig.json +26 -0
  196. package/package.json +88 -34
  197. package/posttsup.ts +24 -0
  198. package/src/Container.tsx +176 -0
  199. package/src/Containers.tsx +85 -0
  200. package/src/ContextContainer.ts +145 -0
  201. package/src/DebugView.tsx +83 -0
  202. package/src/LazyLegendList.tsx +41 -0
  203. package/src/LeanView.tsx +18 -0
  204. package/src/LegendList.tsx +558 -0
  205. package/src/ListComponent.tsx +191 -0
  206. package/src/ScrollAdjust.tsx +24 -0
  207. package/src/ScrollAdjustHandler.ts +26 -0
  208. package/src/Separator.tsx +14 -0
  209. package/src/animated.tsx +6 -0
  210. package/src/calculateItemsInView.ts +363 -0
  211. package/src/calculateOffsetForIndex.ts +23 -0
  212. package/src/calculateOffsetWithOffsetPosition.ts +26 -0
  213. package/src/checkAllSizesKnown.ts +17 -0
  214. package/src/checkAtBottom.ts +36 -0
  215. package/src/checkAtTop.ts +27 -0
  216. package/src/checkThreshold.ts +30 -0
  217. package/src/constants.ts +11 -0
  218. package/src/createColumnWrapperStyle.ts +16 -0
  219. package/src/doInitialAllocateContainers.ts +40 -0
  220. package/src/doMaintainScrollAtEnd.ts +34 -0
  221. package/src/findAvailableContainers.ts +98 -0
  222. package/src/finishScrollTo.ts +8 -0
  223. package/src/getId.ts +21 -0
  224. package/src/getItemSize.ts +52 -0
  225. package/src/getRenderedItem.ts +34 -0
  226. package/src/getScrollVelocity.ts +47 -0
  227. package/src/handleLayout.ts +70 -0
  228. package/src/helpers.ts +39 -0
  229. package/src/index.ts +11 -0
  230. package/src/keyboard-controller.tsx +63 -0
  231. package/src/onScroll.ts +66 -0
  232. package/src/prepareMVCP.ts +50 -0
  233. package/src/reanimated.tsx +63 -0
  234. package/src/requestAdjust.ts +41 -0
  235. package/src/scrollTo.ts +40 -0
  236. package/src/scrollToIndex.ts +34 -0
  237. package/src/setDidLayout.ts +25 -0
  238. package/src/setPaddingTop.ts +28 -0
  239. package/src/state.tsx +304 -0
  240. package/src/types.ts +610 -0
  241. package/src/updateAlignItemsPaddingTop.ts +18 -0
  242. package/src/updateAllPositions.ts +130 -0
  243. package/src/updateItemSize.ts +203 -0
  244. package/src/updateTotalSize.ts +44 -0
  245. package/src/useAnimatedValue.ts +6 -0
  246. package/src/useCombinedRef.ts +22 -0
  247. package/src/useInit.ts +17 -0
  248. package/src/useSyncLayout.tsx +68 -0
  249. package/src/useValue$.ts +53 -0
  250. package/src/viewability.ts +279 -0
  251. package/tsconfig.json +59 -0
  252. package/tsup.config.ts +21 -0
  253. package/index.js +0 -2348
  254. package/index.mjs +0 -2320
  255. /package/{animated.js → dist/animated.js} +0 -0
  256. /package/{animated.mjs → dist/animated.mjs} +0 -0
  257. /package/{keyboard-controller.js → dist/keyboard-controller.js} +0 -0
  258. /package/{keyboard-controller.mjs → dist/keyboard-controller.mjs} +0 -0
  259. /package/{reanimated.d.mts → dist/reanimated.d.mts} +0 -0
  260. /package/{reanimated.d.ts → dist/reanimated.d.ts} +0 -0
  261. /package/{reanimated.js → dist/reanimated.js} +0 -0
  262. /package/{reanimated.mjs → dist/reanimated.mjs} +0 -0
@@ -0,0 +1,247 @@
1
+ import { LegendList } from "@legendapp/list";
2
+ import { useHeaderHeight } from "@react-navigation/elements";
3
+ import { Fragment, useState } from "react";
4
+ import { KeyboardAvoidingView, Platform, Pressable, StyleSheet, Text, View } from "react-native";
5
+ import { SafeAreaView } from "react-native-safe-area-context";
6
+
7
+ type Message = {
8
+ id: string;
9
+ text: string;
10
+ sender: "user" | "bot";
11
+ timeStamp: number;
12
+ };
13
+
14
+ let idCounter = 0;
15
+ const MS_PER_SECOND = 1000;
16
+
17
+ const defaultChatMessages: Message[] = [
18
+ {
19
+ id: String(idCounter++),
20
+ text: "Hi, I have a question about your product",
21
+ sender: "user",
22
+ timeStamp: Date.now() - MS_PER_SECOND * 5,
23
+ },
24
+ {
25
+ id: String(idCounter++),
26
+ text: "Hello there! How can I assist you today?",
27
+ sender: "bot",
28
+ timeStamp: Date.now() - MS_PER_SECOND * 4,
29
+ },
30
+ {
31
+ id: String(idCounter++),
32
+ text: "I'm looking for information about pricing plans",
33
+ sender: "user",
34
+ timeStamp: Date.now() - MS_PER_SECOND * 4,
35
+ },
36
+ {
37
+ id: String(idCounter++),
38
+ text: "We offer several pricing tiers based on your needs",
39
+ sender: "bot",
40
+ timeStamp: Date.now() - MS_PER_SECOND * 4,
41
+ },
42
+ {
43
+ id: String(idCounter++),
44
+ text: "Our basic plan starts at $9.99 per month",
45
+ sender: "bot",
46
+ timeStamp: Date.now() - MS_PER_SECOND * 4,
47
+ },
48
+ {
49
+ id: String(idCounter++),
50
+ text: "Do you offer any discounts for annual billing?",
51
+ sender: "user",
52
+ timeStamp: Date.now() - MS_PER_SECOND * 4,
53
+ },
54
+ {
55
+ id: String(idCounter++),
56
+ text: "Yes! You can save 20% with our annual billing option",
57
+ sender: "bot",
58
+ timeStamp: Date.now() - MS_PER_SECOND * 4,
59
+ },
60
+ {
61
+ id: String(idCounter++),
62
+ text: "That sounds great. What features are included?",
63
+ sender: "user",
64
+ timeStamp: Date.now() - MS_PER_SECOND * 4,
65
+ },
66
+ {
67
+ id: String(idCounter++),
68
+ text: "The basic plan includes all core features plus 10GB storage",
69
+ sender: "bot",
70
+ timeStamp: Date.now() - MS_PER_SECOND * 4,
71
+ },
72
+ {
73
+ id: String(idCounter++),
74
+ text: "Premium plans include priority support and additional tools",
75
+ sender: "bot",
76
+ timeStamp: Date.now() - MS_PER_SECOND * 4,
77
+ },
78
+ {
79
+ id: String(idCounter++),
80
+ text: "I think the basic plan would work for my needs",
81
+ sender: "user",
82
+ timeStamp: Date.now() - MS_PER_SECOND * 4,
83
+ },
84
+ {
85
+ id: String(idCounter++),
86
+ text: "Perfect! I can help you get set up with that",
87
+ sender: "bot",
88
+ timeStamp: Date.now() - MS_PER_SECOND * 4,
89
+ },
90
+ {
91
+ id: String(idCounter++),
92
+ text: "Thanks for your help so far",
93
+ sender: "user",
94
+ timeStamp: Date.now() - MS_PER_SECOND * 4,
95
+ },
96
+ {
97
+ id: String(idCounter++),
98
+ text: "You're welcome! Is there anything else I can assist with today?",
99
+ sender: "bot",
100
+ timeStamp: Date.now() - MS_PER_SECOND * 3,
101
+ },
102
+ ];
103
+
104
+ const ChatResizeOuter = () => {
105
+ const [messages, setMessages] = useState<Message[]>(defaultChatMessages);
106
+ const [inputText, setInputText] = useState("");
107
+ const headerHeight = Platform.OS === "ios" ? useHeaderHeight() : 80;
108
+ const [buttonHeight, setButtonHeight] = useState(40);
109
+
110
+ const sendMessage = () => {
111
+ const text = inputText || "Empty message";
112
+ if (text.trim()) {
113
+ setMessages((messages) => [
114
+ ...messages,
115
+ { id: String(idCounter++), text: text, sender: "user", timeStamp: Date.now() },
116
+ ]);
117
+ setInputText("");
118
+ setTimeout(() => {
119
+ setMessages((messages) => [
120
+ ...messages,
121
+ {
122
+ id: String(idCounter++),
123
+ text: `Answer: ${text.toUpperCase()}`,
124
+ sender: "bot",
125
+ timeStamp: Date.now(),
126
+ },
127
+ ]);
128
+ }, 300);
129
+ }
130
+ };
131
+
132
+ return (
133
+ <SafeAreaView style={styles.container} edges={["bottom"]}>
134
+ <KeyboardAvoidingView
135
+ style={styles.container}
136
+ behavior="padding"
137
+ keyboardVerticalOffset={headerHeight}
138
+ contentContainerStyle={{ flex: 1 }}
139
+ >
140
+ <LegendList
141
+ data={messages}
142
+ contentContainerStyle={styles.contentContainer}
143
+ keyExtractor={(item) => item.id}
144
+ estimatedItemSize={10} // A size that's way too small to check the behavior is correct
145
+ initialScrollIndex={messages.length - 1}
146
+ maintainVisibleContentPosition
147
+ maintainScrollAtEnd
148
+ maintainScrollAtEndThreshold={0.1}
149
+ alignItemsAtEnd
150
+ renderItem={renderItem}
151
+ />
152
+ </KeyboardAvoidingView>
153
+ <Pressable
154
+ style={{
155
+ height: buttonHeight,
156
+ backgroundColor: "rgba(0, 0, 0, 0.1)",
157
+ alignItems: "center",
158
+ justifyContent: "center",
159
+ }}
160
+ onPress={() => {
161
+ setButtonHeight(buttonHeight === 40 ? 300 : 40);
162
+ }}
163
+ >
164
+ <Text style={{ fontSize: 16, fontWeight: "medium" }}>Resize</Text>
165
+ </Pressable>
166
+ </SafeAreaView>
167
+ );
168
+ };
169
+
170
+ const styles = StyleSheet.create({
171
+ container: {
172
+ flex: 1,
173
+ backgroundColor: "#fff",
174
+ },
175
+ contentContainer: {
176
+ paddingHorizontal: 16,
177
+ },
178
+ messageContainer: {
179
+ padding: 16,
180
+ borderRadius: 16,
181
+ marginVertical: 4,
182
+ },
183
+ messageText: {
184
+ fontSize: 16,
185
+ },
186
+ userMessageText: {
187
+ color: "white",
188
+ },
189
+ inputContainer: {
190
+ flexDirection: "row",
191
+ alignItems: "center",
192
+ padding: 10,
193
+ borderTopWidth: 1,
194
+ borderColor: "#ccc",
195
+ },
196
+ botMessageContainer: {
197
+ backgroundColor: "#f1f1f1",
198
+ },
199
+ userMessageContainer: {
200
+ backgroundColor: "#007AFF",
201
+ },
202
+ botStyle: {
203
+ maxWidth: "75%",
204
+ alignSelf: "flex-start",
205
+ },
206
+ userStyle: {
207
+ maxWidth: "75%",
208
+ alignSelf: "flex-end",
209
+ alignItems: "flex-end",
210
+ },
211
+ input: {
212
+ flex: 1,
213
+ borderWidth: 1,
214
+ borderColor: "#ccc",
215
+ borderRadius: 5,
216
+ padding: 10,
217
+ marginRight: 10,
218
+ },
219
+ timeStamp: {
220
+ marginVertical: 5,
221
+ },
222
+ timeStampText: {
223
+ fontSize: 12,
224
+ color: "#888",
225
+ },
226
+ });
227
+
228
+ export default ChatResizeOuter;
229
+
230
+ const renderItem = ({ item }: { item: Message }) => {
231
+ return (
232
+ <Fragment>
233
+ <View
234
+ style={[
235
+ styles.messageContainer,
236
+ item.sender === "bot" ? styles.botMessageContainer : styles.userMessageContainer,
237
+ item.sender === "bot" ? styles.botStyle : styles.userStyle,
238
+ ]}
239
+ >
240
+ <Text style={[styles.messageText, item.sender === "user" && styles.userMessageText]}>{item.text}</Text>
241
+ </View>
242
+ <View style={[styles.timeStamp, item.sender === "bot" ? styles.botStyle : styles.userStyle]}>
243
+ <Text style={styles.timeStampText}>{new Date(item.timeStamp).toLocaleTimeString()}</Text>
244
+ </View>
245
+ </Fragment>
246
+ );
247
+ };
@@ -0,0 +1,78 @@
1
+ import { LegendList } from "@legendapp/list";
2
+ import { useState } from "react";
3
+ import { useEffect } from "react";
4
+ import { LogBox, StyleSheet, Text, View } from "react-native";
5
+
6
+ LogBox.ignoreLogs(["Open debugger"]);
7
+
8
+ const initialData = Array.from({ length: 8 }, (_, index) => ({ id: index.toString() }));
9
+
10
+ export default function Columns() {
11
+ const [data, setData] = useState(initialData);
12
+
13
+ useEffect(() => {
14
+ setTimeout(() => {
15
+ setData(Array.from({ length: 20 }, (_, index) => ({ id: index.toString() })));
16
+ }, 1000);
17
+ });
18
+
19
+ return (
20
+ <View style={styles.container}>
21
+ <LegendList
22
+ data={data}
23
+ renderItem={Item}
24
+ keyExtractor={(item) => item.id}
25
+ numColumns={3}
26
+ columnWrapperStyle={{
27
+ columnGap: 16,
28
+ rowGap: 16,
29
+ }}
30
+ />
31
+ </View>
32
+ );
33
+ }
34
+
35
+ function Item({ item }: { item: { id: string } }) {
36
+ return (
37
+ <View style={styles.redRectangle}>
38
+ <View style={styles.redRectangleInner} />
39
+ <Text>Item {item.id}</Text>
40
+ </View>
41
+ );
42
+ }
43
+
44
+ const styles = StyleSheet.create({
45
+ container: {
46
+ flex: 1,
47
+ backgroundColor: "#fff",
48
+ },
49
+ redRectangle: {
50
+ aspectRatio: 1,
51
+ },
52
+ redRectangleInner: {
53
+ height: "100%",
54
+ width: "100%",
55
+ backgroundColor: "red",
56
+ borderRadius: 8,
57
+ },
58
+ columnWrapper: {
59
+ justifyContent: "space-between",
60
+ },
61
+ listHeader: {
62
+ alignSelf: "center",
63
+ height: 100,
64
+ width: 100,
65
+ backgroundColor: "#456AAA",
66
+ borderRadius: 12,
67
+ marginHorizontal: 8,
68
+ marginVertical: 8,
69
+ },
70
+ listEmpty: {
71
+ flex: 1,
72
+ justifyContent: "center",
73
+ alignItems: "center",
74
+ backgroundColor: "#6789AB",
75
+ paddingVertical: 16,
76
+ height: 100,
77
+ },
78
+ });
@@ -0,0 +1,182 @@
1
+ import { LegendList } from "@legendapp/list";
2
+ import { type TCountryCode, countries, getEmojiFlag } from "countries-list";
3
+ import { useMemo, useState } from "react";
4
+ import { Pressable, StatusBar, StyleSheet, Text, TextInput, View } from "react-native";
5
+ import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
6
+
7
+ export const unstable_settings = {
8
+ initialRouteName: "index",
9
+ };
10
+
11
+ export const createTitle = () => "Countries";
12
+
13
+ type Country = {
14
+ id: string;
15
+ name: string;
16
+ flag: string;
17
+ };
18
+
19
+ // Convert countries object to array and add an id
20
+ const DATA: Country[] = Object.entries(countries)
21
+ .map(([code, country]) => ({
22
+ id: code,
23
+ name: country.name,
24
+ flag: getEmojiFlag(code as TCountryCode),
25
+ }))
26
+ .sort((a, b) => a.name.localeCompare(b.name));
27
+
28
+ type ItemProps = {
29
+ item: Country;
30
+ onPress: () => void;
31
+ isSelected: boolean;
32
+ };
33
+
34
+ const Item = ({ item, onPress, isSelected }: ItemProps) => (
35
+ <Pressable
36
+ onPress={onPress}
37
+ style={({ pressed }) => [styles.item, isSelected && styles.selectedItem, pressed && styles.pressedItem]}
38
+ >
39
+ <View style={styles.flagContainer}>
40
+ <Text style={styles.flag}>{item.flag}</Text>
41
+ </View>
42
+ <View style={styles.contentContainer}>
43
+ <Text style={[styles.title, isSelected && styles.selectedText]}>
44
+ {item.name}
45
+ <Text style={styles.countryCode}> ({item.id})</Text>
46
+ </Text>
47
+ </View>
48
+ </Pressable>
49
+ );
50
+
51
+ const App = () => {
52
+ const [selectedId, setSelectedId] = useState<string>();
53
+ const [searchQuery, setSearchQuery] = useState("");
54
+
55
+ const filteredData = useMemo(() => {
56
+ const query = searchQuery.toLowerCase();
57
+ return DATA.filter(
58
+ (country) => country.name.toLowerCase().includes(query) || country.id.toLowerCase().includes(query),
59
+ );
60
+ }, [searchQuery]);
61
+
62
+ const renderItem = ({ item }: { item: Country }) => {
63
+ const isSelected = item.id === selectedId;
64
+ return <Item item={item} onPress={() => setSelectedId(item.id)} isSelected={isSelected} />;
65
+ };
66
+
67
+ return (
68
+ <SafeAreaProvider>
69
+ <SafeAreaView style={styles.container}>
70
+ <View style={styles.searchContainer}>
71
+ <TextInput
72
+ style={styles.searchInput}
73
+ placeholder="Search country or code..."
74
+ value={searchQuery}
75
+ onChangeText={setSearchQuery}
76
+ clearButtonMode="while-editing"
77
+ autoCapitalize="none"
78
+ autoCorrect={false}
79
+ />
80
+ </View>
81
+ <LegendList
82
+ data={filteredData}
83
+ renderItem={renderItem}
84
+ keyExtractor={(item) => item.id}
85
+ extraData={selectedId}
86
+ estimatedItemSize={70}
87
+ recycleItems
88
+ onStartReachedThreshold={0.1}
89
+ onStartReached={({ distanceFromStart }) => {
90
+ console.log("onStartReached", distanceFromStart);
91
+ }}
92
+ onEndReachedThreshold={0.1}
93
+ onEndReached={({ distanceFromEnd }) => {
94
+ console.log("onEndReached", distanceFromEnd);
95
+ }}
96
+ // ListHeaderComponent={<View style={{ height: 200, backgroundColor: "red" }} />}
97
+ // ListFooterComponent={<View style={{ height: 200, backgroundColor: "blue" }} />}
98
+ // ItemSeparatorComponent={Separator}
99
+ />
100
+ </SafeAreaView>
101
+ </SafeAreaProvider>
102
+ );
103
+ };
104
+
105
+ const Separator = () => <View style={{ height: 40, backgroundColor: "green" }} />;
106
+
107
+ export default App;
108
+
109
+ const styles = StyleSheet.create({
110
+ container: {
111
+ flex: 1,
112
+ marginTop: StatusBar.currentHeight || 0,
113
+ backgroundColor: "#f5f5f5",
114
+ },
115
+ searchContainer: {
116
+ padding: 8,
117
+ backgroundColor: "#fff",
118
+ borderBottomWidth: 1,
119
+ borderBottomColor: "#e0e0e0",
120
+ },
121
+ searchInput: {
122
+ height: 40,
123
+ backgroundColor: "#f5f5f5",
124
+ borderRadius: 8,
125
+ paddingHorizontal: 12,
126
+ fontSize: 16,
127
+ },
128
+ item: {
129
+ paddingHorizontal: 16,
130
+ paddingVertical: 6,
131
+ flexDirection: "row",
132
+ alignItems: "center",
133
+ backgroundColor: "#fff",
134
+ borderRadius: 12,
135
+ // shadowColor: "#000",
136
+ // shadowOffset: {
137
+ // width: 0,
138
+ // height: 2,
139
+ // },
140
+ // shadowOpacity: 0.1,
141
+ // shadowRadius: 3,
142
+ // elevation: 3,
143
+ },
144
+ selectedItem: {
145
+ // backgroundColor: "#e3f2fd",
146
+ // borderColor: "#1976d2",
147
+ // borderWidth: 1,
148
+ },
149
+ pressedItem: {
150
+ // backgroundColor: "#f0f0f0",
151
+ },
152
+ flagContainer: {
153
+ marginRight: 16,
154
+ width: 40,
155
+ height: 40,
156
+ borderRadius: 20,
157
+ backgroundColor: "#f8f9fa",
158
+ alignItems: "center",
159
+ justifyContent: "center",
160
+ },
161
+ flag: {
162
+ fontSize: 28,
163
+ },
164
+ contentContainer: {
165
+ flex: 1,
166
+ justifyContent: "center",
167
+ },
168
+ title: {
169
+ fontSize: 16,
170
+ color: "#333",
171
+ fontWeight: "500",
172
+ },
173
+ selectedText: {
174
+ color: "#1976d2",
175
+ fontWeight: "600",
176
+ },
177
+ countryCode: {
178
+ fontSize: 14,
179
+ color: "#666",
180
+ fontWeight: "400",
181
+ },
182
+ });
@@ -0,0 +1,163 @@
1
+ import { FlashList } from "@shopify/flash-list";
2
+ import { type TCountryCode, countries, getEmojiFlag } from "countries-list";
3
+ import { useMemo, useState } from "react";
4
+ import { Pressable, StatusBar, StyleSheet, Text, TextInput, View } from "react-native";
5
+ import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
6
+
7
+ export const unstable_settings = {
8
+ initialRouteName: "index",
9
+ };
10
+
11
+ export const createTitle = () => "Countries";
12
+
13
+ type Country = {
14
+ id: string;
15
+ name: string;
16
+ flag: string;
17
+ };
18
+
19
+ // Convert countries object to array and add an id
20
+ const DATA: Country[] = Object.entries(countries)
21
+ .map(([code, country]) => ({
22
+ id: code,
23
+ name: country.name,
24
+ flag: getEmojiFlag(code as TCountryCode),
25
+ }))
26
+ .sort((a, b) => a.name.localeCompare(b.name));
27
+
28
+ type ItemProps = {
29
+ item: Country;
30
+ onPress: () => void;
31
+ isSelected: boolean;
32
+ };
33
+
34
+ const Item = ({ item, onPress, isSelected }: ItemProps) => (
35
+ <Pressable
36
+ onPress={onPress}
37
+ style={({ pressed }) => [styles.item, isSelected && styles.selectedItem, pressed && styles.pressedItem]}
38
+ >
39
+ <View style={styles.flagContainer}>
40
+ <Text style={styles.flag}>{item.flag}</Text>
41
+ </View>
42
+ <View style={styles.contentContainer}>
43
+ <Text style={[styles.title, isSelected && styles.selectedText]}>
44
+ {item.name}
45
+ <Text style={styles.countryCode}> ({item.id})</Text>
46
+ </Text>
47
+ </View>
48
+ </Pressable>
49
+ );
50
+
51
+ const App = () => {
52
+ const [selectedId, setSelectedId] = useState<string>();
53
+ const [searchQuery, setSearchQuery] = useState("");
54
+
55
+ const filteredData = useMemo(() => {
56
+ const query = searchQuery.toLowerCase();
57
+ return DATA.filter(
58
+ (country) => country.name.toLowerCase().includes(query) || country.id.toLowerCase().includes(query),
59
+ );
60
+ }, [searchQuery]);
61
+
62
+ const renderItem = ({ item }: { item: Country }) => {
63
+ const isSelected = item.id === selectedId;
64
+ return <Item item={item} onPress={() => setSelectedId(item.id)} isSelected={isSelected} />;
65
+ };
66
+
67
+ return (
68
+ <SafeAreaProvider>
69
+ <SafeAreaView style={styles.container}>
70
+ <View style={styles.searchContainer}>
71
+ <TextInput
72
+ style={styles.searchInput}
73
+ placeholder="Search country or code..."
74
+ value={searchQuery}
75
+ onChangeText={setSearchQuery}
76
+ clearButtonMode="while-editing"
77
+ autoCapitalize="none"
78
+ autoCorrect={false}
79
+ />
80
+ </View>
81
+ <FlashList
82
+ data={filteredData}
83
+ renderItem={renderItem}
84
+ keyExtractor={(item) => item.id}
85
+ extraData={selectedId}
86
+ estimatedItemSize={70}
87
+ //scrollEventThrottle={200}
88
+ disableAutoLayout
89
+ />
90
+ </SafeAreaView>
91
+ </SafeAreaProvider>
92
+ );
93
+ };
94
+
95
+ export default App;
96
+
97
+ const styles = StyleSheet.create({
98
+ container: {
99
+ flex: 1,
100
+ marginTop: StatusBar.currentHeight || 0,
101
+ backgroundColor: "#f5f5f5",
102
+ },
103
+ searchContainer: {
104
+ padding: 8,
105
+ backgroundColor: "#fff",
106
+ borderBottomWidth: 1,
107
+ borderBottomColor: "#e0e0e0",
108
+ },
109
+ searchInput: {
110
+ height: 40,
111
+ backgroundColor: "#f5f5f5",
112
+ borderRadius: 8,
113
+ paddingHorizontal: 12,
114
+ fontSize: 16,
115
+ },
116
+ item: {
117
+ paddingHorizontal: 16,
118
+ paddingVertical: 6,
119
+ flexDirection: "row",
120
+ alignItems: "center",
121
+ backgroundColor: "#fff",
122
+ borderRadius: 12,
123
+ // elevation: 3,
124
+ },
125
+ selectedItem: {
126
+ // backgroundColor: "#e3f2fd",
127
+ // borderColor: "#1976d2",
128
+ // borderWidth: 1,
129
+ },
130
+ pressedItem: {
131
+ // backgroundColor: "#f0f0f0",
132
+ },
133
+ flagContainer: {
134
+ marginRight: 16,
135
+ width: 40,
136
+ height: 40,
137
+ borderRadius: 20,
138
+ backgroundColor: "#f8f9fa",
139
+ alignItems: "center",
140
+ justifyContent: "center",
141
+ },
142
+ flag: {
143
+ fontSize: 28,
144
+ },
145
+ contentContainer: {
146
+ flex: 1,
147
+ justifyContent: "center",
148
+ },
149
+ title: {
150
+ fontSize: 16,
151
+ color: "#333",
152
+ fontWeight: "500",
153
+ },
154
+ selectedText: {
155
+ color: "#1976d2",
156
+ fontWeight: "600",
157
+ },
158
+ countryCode: {
159
+ fontSize: 14,
160
+ color: "#666",
161
+ fontWeight: "400",
162
+ },
163
+ });