|
| 1 | +import { View, ActivityIndicator, FlatList } from "react-native"; |
| 2 | +import { Text } from "~/components/ui/text"; |
| 3 | +import { Button } from "~/components/ui/button"; |
| 4 | +import { |
| 5 | + ActionSheet, |
| 6 | + ActionSheetContent, |
| 7 | + ActionSheetTrigger, |
| 8 | +} from "~/components/ui/action-sheet"; |
| 9 | +import * as ThreadListPrimitive from "@creatorem/ai-react-native/primitives/thread-list"; |
| 10 | +import * as ThreadListItemPrimitive from "@creatorem/ai-react-native/primitives/thread-list-item"; |
| 11 | +import { useThreads } from "@creatorem/ai-react-native/ai-provider"; |
| 12 | +import { useCallback, type FC } from "react"; |
| 13 | +import { useCSSVariable } from "uniwind"; |
| 14 | +import { Icon } from "~/components/ui/icon"; |
| 15 | + |
| 16 | +export const ThreadList: FC = () => { |
| 17 | + const isLoading = useThreads((threads) => threads.isLoading); |
| 18 | + |
| 19 | + return ( |
| 20 | + <ThreadListPrimitive.Root className="flex flex-col gap-1"> |
| 21 | + <ThreadListNew /> |
| 22 | + {isLoading ? <ThreadListSkeleton /> : null} |
| 23 | + {!isLoading ? ( |
| 24 | + <ThreadListPrimitive.Items components={{ ThreadListItem }} /> |
| 25 | + ) : null} |
| 26 | + </ThreadListPrimitive.Root> |
| 27 | + ); |
| 28 | +}; |
| 29 | + |
| 30 | +const ThreadListNew: FC = () => { |
| 31 | + return ( |
| 32 | + <ThreadListPrimitive.New |
| 33 | + variant="outline" |
| 34 | + className="mb-4 h-9 justify-start gap-2 rounded-lg px-3 text-sm hover:bg-muted data-active:bg-muted" |
| 35 | + > |
| 36 | + <Icon name="Plus" className="size-4" /> |
| 37 | + <Text>New Thread</Text> |
| 38 | + </ThreadListPrimitive.New> |
| 39 | + ); |
| 40 | +}; |
| 41 | + |
| 42 | +const ThreadListSkeleton: FC = () => { |
| 43 | + const foregroundColor = useCSSVariable("--color-muted-foreground"); |
| 44 | + |
| 45 | + return ( |
| 46 | + <View className="mt-2 flex flex-col gap-1"> |
| 47 | + {Array.from({ length: 5 }, (_, i) => ( |
| 48 | + <View key={i} className="flex h-10 items-center justify-center px-3"> |
| 49 | + <ActivityIndicator |
| 50 | + size="small" |
| 51 | + color={foregroundColor} |
| 52 | + style={i > 0 ? { opacity: 0 } : undefined} |
| 53 | + /> |
| 54 | + </View> |
| 55 | + ))} |
| 56 | + </View> |
| 57 | + ); |
| 58 | +}; |
| 59 | + |
| 60 | +const ThreadListItem: FC = () => { |
| 61 | + return ( |
| 62 | + <ThreadListItemPrimitive.Root className="group flex h-9 flex-row items-center gap-2 rounded-lg transition-colors hover:bg-muted focus-visible:bg-muted focus-visible:outline-none data-active:bg-muted"> |
| 63 | + <ThreadListItemPrimitive.Trigger className="flex h-full min-w-0 flex-1 items-center justify-start truncate bg-transparent px-3 text-start text-sm"> |
| 64 | + <ThreadListItemPrimitive.Title |
| 65 | + fallback="New Chat" |
| 66 | + className="text-foreground" |
| 67 | + /> |
| 68 | + </ThreadListItemPrimitive.Trigger> |
| 69 | + <ThreadListItemMore /> |
| 70 | + </ThreadListItemPrimitive.Root> |
| 71 | + ); |
| 72 | +}; |
| 73 | + |
| 74 | +const ThreadListItemMore: FC = () => { |
| 75 | + const textMutedForeground = useCSSVariable("--color-muted-foreground"); |
| 76 | + return ( |
| 77 | + <ActionSheet> |
| 78 | + <ActionSheetTrigger asChild> |
| 79 | + <Button size="icon" variant="ghost"> |
| 80 | + <Icon name="MoreHorizontal" size={20} color={textMutedForeground} /> |
| 81 | + </Button> |
| 82 | + </ActionSheetTrigger> |
| 83 | + <ActionSheetContent> |
| 84 | + <View className="p-6"> |
| 85 | + <ThreadListItemPrimitive.Archive> |
| 86 | + <Icon name="Archive" className="size-4" /> |
| 87 | + <Text>Archive</Text> |
| 88 | + </ThreadListItemPrimitive.Archive> |
| 89 | + </View> |
| 90 | + </ActionSheetContent> |
| 91 | + </ActionSheet> |
| 92 | + ); |
| 93 | +}; |
0 commit comments