{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "AnimatedList-TS-TW", "title": "AnimatedList", "description": "List items enter with staggered motion variants for polished reveals.", "type": "registry:component", "files": [ { "type": "registry:component", "path": "AnimatedList/AnimatedList.tsx", "content": "import React, { useRef, useState, useEffect, useCallback, ReactNode, MouseEventHandler, UIEvent } from 'react';\nimport { motion, useInView } from 'motion/react';\n\ninterface AnimatedItemProps {\n children: ReactNode;\n delay?: number;\n index: number;\n onMouseEnter?: MouseEventHandler;\n onClick?: MouseEventHandler;\n}\n\nconst AnimatedItem: React.FC = ({ children, delay = 0, index, onMouseEnter, onClick }) => {\n const ref = useRef(null);\n const inView = useInView(ref, { amount: 0.5, once: false });\n return (\n \n {children}\n \n );\n};\n\ninterface AnimatedListProps {\n items?: string[];\n onItemSelect?: (item: string, index: number) => void;\n showGradients?: boolean;\n enableArrowNavigation?: boolean;\n className?: string;\n itemClassName?: string;\n displayScrollbar?: boolean;\n initialSelectedIndex?: number;\n}\n\nconst AnimatedList: React.FC = ({\n items = [\n 'Item 1',\n 'Item 2',\n 'Item 3',\n 'Item 4',\n 'Item 5',\n 'Item 6',\n 'Item 7',\n 'Item 8',\n 'Item 9',\n 'Item 10',\n 'Item 11',\n 'Item 12',\n 'Item 13',\n 'Item 14',\n 'Item 15'\n ],\n onItemSelect,\n showGradients = true,\n enableArrowNavigation = true,\n className = '',\n itemClassName = '',\n displayScrollbar = true,\n initialSelectedIndex = -1\n}) => {\n const listRef = useRef(null);\n const [selectedIndex, setSelectedIndex] = useState(initialSelectedIndex);\n const [keyboardNav, setKeyboardNav] = useState(false);\n const [topGradientOpacity, setTopGradientOpacity] = useState(0);\n const [bottomGradientOpacity, setBottomGradientOpacity] = useState(1);\n\n const handleItemMouseEnter = useCallback((index: number) => {\n setSelectedIndex(index);\n }, []);\n\n const handleItemClick = useCallback(\n (item: string, index: number) => {\n setSelectedIndex(index);\n if (onItemSelect) {\n onItemSelect(item, index);\n }\n },\n [onItemSelect]\n );\n\n const handleScroll = (e: UIEvent) => {\n const { scrollTop, scrollHeight, clientHeight } = e.target as HTMLDivElement;\n setTopGradientOpacity(Math.min(scrollTop / 50, 1));\n const bottomDistance = scrollHeight - (scrollTop + clientHeight);\n setBottomGradientOpacity(scrollHeight <= clientHeight ? 0 : Math.min(bottomDistance / 50, 1));\n };\n\n useEffect(() => {\n if (!enableArrowNavigation) return;\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'ArrowDown' || (e.key === 'Tab' && !e.shiftKey)) {\n e.preventDefault();\n setKeyboardNav(true);\n setSelectedIndex(prev => Math.min(prev + 1, items.length - 1));\n } else if (e.key === 'ArrowUp' || (e.key === 'Tab' && e.shiftKey)) {\n e.preventDefault();\n setKeyboardNav(true);\n setSelectedIndex(prev => Math.max(prev - 1, 0));\n } else if (e.key === 'Enter') {\n if (selectedIndex >= 0 && selectedIndex < items.length) {\n e.preventDefault();\n if (onItemSelect) {\n onItemSelect(items[selectedIndex], selectedIndex);\n }\n }\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n return () => window.removeEventListener('keydown', handleKeyDown);\n }, [items, selectedIndex, onItemSelect, enableArrowNavigation]);\n\n useEffect(() => {\n if (!keyboardNav || selectedIndex < 0 || !listRef.current) return;\n const container = listRef.current;\n const selectedItem = container.querySelector(`[data-index=\"${selectedIndex}\"]`) as HTMLElement | null;\n if (selectedItem) {\n const extraMargin = 50;\n const containerScrollTop = container.scrollTop;\n const containerHeight = container.clientHeight;\n const itemTop = selectedItem.offsetTop;\n const itemBottom = itemTop + selectedItem.offsetHeight;\n if (itemTop < containerScrollTop + extraMargin) {\n container.scrollTo({ top: itemTop - extraMargin, behavior: 'smooth' });\n } else if (itemBottom > containerScrollTop + containerHeight - extraMargin) {\n container.scrollTo({\n top: itemBottom - containerHeight + extraMargin,\n behavior: 'smooth'\n });\n }\n }\n setKeyboardNav(false);\n }, [selectedIndex, keyboardNav]);\n\n return (\n
\n \n {items.map((item, index) => (\n handleItemMouseEnter(index)}\n onClick={() => handleItemClick(item, index)}\n >\n
\n

{item}

\n
\n \n ))}\n
\n {showGradients && (\n <>\n \n \n \n )}\n \n );\n};\n\nexport default AnimatedList;\n" } ], "registryDependencies": [], "dependencies": [ "motion@^12.23.12" ] }