{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "AnimatedList-JS-CSS", "title": "AnimatedList", "description": "List items enter with staggered motion variants for polished reveals.", "type": "registry:component", "files": [ { "type": "registry:component", "path": "AnimatedList/AnimatedList.css", "content": ".scroll-list-container {\n position: relative;\n width: 500px;\n}\n\n.scroll-list {\n max-height: 400px;\n overflow-y: auto;\n padding: 16px;\n}\n\n.scroll-list::-webkit-scrollbar {\n width: 8px;\n}\n\n.scroll-list::-webkit-scrollbar-track {\n background: #120F17;\n}\n\n.scroll-list::-webkit-scrollbar-thumb {\n background: #2F293A;\n border-radius: 4px;\n}\n\n.no-scrollbar::-webkit-scrollbar {\n display: none;\n}\n\n.no-scrollbar {\n -ms-overflow-style: none;\n scrollbar-width: none;\n}\n\n.item {\n padding: 16px;\n background-color: #2F293A;\n border-radius: 8px;\n margin-bottom: 1rem;\n}\n\n.item.selected {\n background-color: #2F293A;\n}\n\n.item-text {\n color: white;\n margin: 0;\n}\n\n.top-gradient {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 50px;\n background: linear-gradient(to bottom, #120F17, transparent);\n pointer-events: none;\n transition: opacity 0.3s ease;\n}\n\n.bottom-gradient {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: 100px;\n background: linear-gradient(to top, #120F17, transparent);\n pointer-events: none;\n transition: opacity 0.3s ease;\n}\n" }, { "type": "registry:component", "path": "AnimatedList/AnimatedList.jsx", "content": "import { useRef, useState, useEffect, useCallback } from 'react';\nimport { motion, useInView } from 'motion/react';\nimport './AnimatedList.css';\n\nconst AnimatedItem = ({ children, delay = 0, index, onMouseEnter, onClick }) => {\n const ref = useRef(null);\n const inView = useInView(ref, { amount: 0.5, triggerOnce: false });\n return (\n \n {children}\n \n );\n};\n\nconst AnimatedList = ({\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 => {\n setSelectedIndex(index);\n }, []);\n\n const handleItemClick = useCallback(\n (item, index) => {\n setSelectedIndex(index);\n if (onItemSelect) {\n onItemSelect(item, index);\n }\n },\n [onItemSelect]\n );\n\n const handleScroll = useCallback(e => {\n const { scrollTop, scrollHeight, clientHeight } = e.target;\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 => {\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}\"]`);\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" ] }