{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "pin-list", "type": "registry:ui", "title": "Pin List", "description": "Pin List Component", "dependencies": [ "motion" ], "registryDependencies": [], "files": [ { "path": "registry/components/pin-list/index.tsx", "content": "'use client';\n\nimport * as React from 'react';\nimport { Pin } from 'lucide-react';\nimport {\n motion,\n LayoutGroup,\n AnimatePresence,\n type HTMLMotionProps,\n type Transition,\n} from 'motion/react';\nimport { cn } from '@/lib/utils';\n\ntype PinListItem = {\n id: number;\n name: string;\n info: string;\n icon: React.ElementType;\n pinned: boolean;\n};\n\ntype PinListProps = {\n items: PinListItem[];\n labels?: {\n pinned?: string;\n unpinned?: string;\n };\n transition?: Transition;\n labelMotionProps?: HTMLMotionProps<'p'>;\n className?: string;\n labelClassName?: string;\n pinnedSectionClassName?: string;\n unpinnedSectionClassName?: string;\n zIndexResetDelay?: number;\n} & HTMLMotionProps<'div'>;\n\nfunction PinList({\n items,\n labels = { pinned: 'Pinned Items', unpinned: 'All Items' },\n transition = { stiffness: 320, damping: 20, mass: 0.8, type: 'spring' },\n labelMotionProps = {\n initial: { opacity: 0 },\n animate: { opacity: 1 },\n exit: { opacity: 0 },\n transition: { duration: 0.22, ease: 'easeInOut' },\n },\n className,\n labelClassName,\n pinnedSectionClassName,\n unpinnedSectionClassName,\n zIndexResetDelay = 500,\n ...props\n}: PinListProps) {\n const [listItems, setListItems] = React.useState(items);\n const [togglingGroup, setTogglingGroup] = React.useState<\n 'pinned' | 'unpinned' | null\n >(null);\n\n const pinned = listItems.filter((u) => u.pinned);\n const unpinned = listItems.filter((u) => !u.pinned);\n\n const toggleStatus = (id: number) => {\n const item = listItems.find((u) => u.id === id);\n if (!item) return;\n\n setTogglingGroup(item.pinned ? 'pinned' : 'unpinned');\n setListItems((prev) => {\n const idx = prev.findIndex((u) => u.id === id);\n if (idx === -1) return prev;\n const updated = [...prev];\n const [item] = updated.splice(idx, 1);\n if (!item) return prev;\n const toggled = { ...item, pinned: !item.pinned };\n if (toggled.pinned) updated.push(toggled);\n else updated.unshift(toggled);\n return updated;\n });\n // Reset group z-index after the animation duration (keep in sync with animation timing)\n setTimeout(() => setTogglingGroup(null), zIndexResetDelay);\n };\n\n return (\n \n \n
\n \n {pinned.length > 0 && (\n \n {labels.pinned}\n \n )}\n \n {pinned.length > 0 && (\n \n {pinned.map((item) => (\n toggleStatus(item.id)}\n transition={transition}\n className=\"flex items-center justify-between gap-5 rounded-2xl bg-neutral-200 dark:bg-neutral-800 p-2\"\n >\n
\n
\n \n
\n
\n
{item.name}
\n
\n {item.info}\n
\n
\n
\n
\n \n
\n \n ))}\n
\n )}\n \n\n
\n \n {unpinned.length > 0 && (\n \n {labels.unpinned}\n \n )}\n \n {unpinned.length > 0 && (\n \n {unpinned.map((item) => (\n toggleStatus(item.id)}\n transition={transition}\n className=\"flex items-center justify-between gap-5 rounded-2xl bg-neutral-200 dark:bg-neutral-800 p-2 group\"\n >\n
\n
\n \n
\n
\n
{item.name}
\n
\n {item.info}\n
\n
\n
\n
\n \n
\n \n ))}\n
\n )}\n \n
\n
\n );\n}\n\nexport { PinList, type PinListProps, type PinListItem };\n", "type": "registry:ui", "target": "components/animate-ui/components/pin-list.tsx" } ] }