{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "animated-list", "type": "registry:ui", "title": "Animated List", "description": "Staggered entrance list (FlashList optional).", "registryDependencies": [ "https://raw.githubusercontent.com/truongnat/rnui/main/registry/dist/nativewind/theme.json" ], "files": [ { "path": "registry/shared/ui/animated-list.tsx", "type": "registry:ui", "target": "components/ui/animated-list.tsx", "content": "import {\n forwardRef,\n memo,\n useCallback,\n useEffect,\n useRef,\n type ComponentType,\n type ReactElement,\n type ReactNode,\n type Ref,\n} from 'react';\nimport {\n Animated,\n FlatList,\n type FlatListProps,\n type ListRenderItemInfo,\n type StyleProp,\n type ViewStyle,\n} from 'react-native';\n\ntype AnyFlatList = ComponentType>;\n\nlet cachedFlashList: AnyFlatList | null | undefined;\n\nfunction loadFlashList(): AnyFlatList | null {\n if (cachedFlashList !== undefined) return cachedFlashList;\n try {\n // Optional peer dep — falls back to FlatList when not installed.\n const mod = require('@shopify/flash-list') as {\n FlashList?: AnyFlatList;\n };\n cachedFlashList = mod.FlashList ?? null;\n } catch {\n cachedFlashList = null;\n }\n return cachedFlashList;\n}\n\nconst MAX_STAGGER_MS = 1000;\n\ninterface AnimatedCellProps {\n index: number;\n /** Delay between staggered items (ms). */\n itemDelay: number;\n /** Disable the entrance animation. */\n animated: boolean;\n itemContainerStyle?: StyleProp;\n children?: ReactNode;\n}\n\n/** Per-item fade+rise entrance; delay staggers by index. */\nfunction AnimatedCellInner({\n index,\n itemDelay,\n animated,\n itemContainerStyle,\n children,\n}: AnimatedCellProps) {\n const progress = useRef(new Animated.Value(animated ? 0 : 1)).current;\n\n useEffect(() => {\n if (!animated) return;\n Animated.timing(progress, {\n toValue: 1,\n duration: 260,\n delay: Math.min(index * itemDelay, MAX_STAGGER_MS),\n useNativeDriver: true,\n }).start();\n }, [animated, index, itemDelay, progress]);\n\n return (\n \n {children}\n \n );\n}\n\nconst AnimatedCell = memo(AnimatedCellInner);\n\nexport interface AnimatedListProps\n extends Omit, 'renderItem'> {\n /** Items to render. */\n data: readonly T[] | null | undefined;\n renderItem: (info: ListRenderItemInfo) => ReactElement | null;\n /** Staggered entrance: delay between items in ms (0 disables animation). Default 50. */\n itemDelay?: number;\n /** Disable the entrance animation entirely. */\n animated?: boolean;\n /** Style for each animated item wrapper. */\n itemContainerStyle?: StyleProp;\n}\n\n/**\n * AnimatedList — FlatList (or FlashList when installed) with a staggered\n * fade-in-down entrance per item. Zero deps beyond RN Animated.\n */\nfunction AnimatedListInner(\n {\n data,\n renderItem,\n itemDelay = 50,\n animated = true,\n itemContainerStyle,\n ...listProps\n }: AnimatedListProps,\n ref: Ref>\n) {\n const ListImpl = (loadFlashList() ?? FlatList) as ComponentType<\n FlatListProps & { ref?: Ref> }\n >;\n\n const renderAnimatedItem = useCallback(\n (info: ListRenderItemInfo) => (\n \n {renderItem(info)}\n \n ),\n [renderItem, itemDelay, animated, itemContainerStyle]\n );\n\n return (\n \n );\n}\n\nexport const AnimatedList = forwardRef(AnimatedListInner) as (\n props: AnimatedListProps & { ref?: Ref> }\n) => ReactElement;\n" } ] }