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

{item}

\r\n
\r\n \r\n ))}\r\n
\r\n {showGradients && (\r\n <>\r\n
\r\n
\r\n \r\n )}\r\n
\r\n );\r\n};\r\n\r\nexport default AnimatedList;\r\n", "type": "registry:component" }, { "path": "public/ts/default/src/ts-default/Components/AnimatedList/AnimatedList.css", "content": ".scroll-list-container {\r\n position: relative;\r\n width: 500px;\r\n}\r\n\r\n.scroll-list {\r\n max-height: 400px;\r\n overflow-y: auto;\r\n padding: 16px;\r\n}\r\n\r\n.scroll-list::-webkit-scrollbar {\r\n width: 8px;\r\n}\r\n\r\n.scroll-list::-webkit-scrollbar-track {\r\n background: #060606;\r\n}\r\n\r\n.scroll-list::-webkit-scrollbar-thumb {\r\n background: #222;\r\n border-radius: 4px;\r\n}\r\n\r\n.no-scrollbar::-webkit-scrollbar {\r\n display: none;\r\n}\r\n\r\n.no-scrollbar {\r\n -ms-overflow-style: none;\r\n scrollbar-width: none;\r\n}\r\n\r\n.item {\r\n padding: 16px;\r\n background-color: #111;\r\n border-radius: 8px;\r\n margin-bottom: 1rem;\r\n}\r\n\r\n.item.selected {\r\n background-color: #222;\r\n}\r\n\r\n.item-text {\r\n color: white;\r\n margin: 0;\r\n}\r\n\r\n.top-gradient {\r\n position: absolute;\r\n top: 0;\r\n left: 0;\r\n right: 0;\r\n height: 50px;\r\n background: linear-gradient(to bottom, #060010, transparent);\r\n pointer-events: none;\r\n transition: opacity 0.3s ease;\r\n}\r\n\r\n.bottom-gradient {\r\n position: absolute;\r\n bottom: 0;\r\n left: 0;\r\n right: 0;\r\n height: 100px;\r\n background: linear-gradient(to top, #060010, transparent);\r\n pointer-events: none;\r\n transition: opacity 0.3s ease;\r\n}\r\n", "type": "registry:item" } ] }