Componentes
[comunidad] · animate-ui / motion / lucide-react

PinList

Lista con reordenamiento fluido: fija ítems con el pin y se mueven entre secciones con spring.

animate-uimotionlucide-react
@/components/animate-ui/components/community/pin-list
RadialChart
Gráfica circular
Dock
Navegación flotante

All Items

PinList
Lista con fijados
CodeTabs
Tabs de código
SparklesText
Texto con destellos
1'use client';;
2import * as React from 'react';
3import { Pin } from 'lucide-react';
4import { motion, LayoutGroup, AnimatePresence } from 'motion/react';
5import { cn } from '@/lib/utils';
6 
7function PinList({
8 items,
9 labels = { pinned: 'Pinned Items', unpinned: 'All Items' },
10 transition = { stiffness: 320, damping: 20, mass: 0.8, type: 'spring' },
11 
12 labelMotionProps = {
13 initial: { opacity: 0 },
14 animate: { opacity: 1 },
15 exit: { opacity: 0 },
16 transition: { duration: 0.22, ease: 'easeInOut' },
17 },
18 
19 className,
20 labelClassName,
21 pinnedSectionClassName,
22 unpinnedSectionClassName,
23 zIndexResetDelay = 500,
24 ...props
25}) {
26 const [listItems, setListItems] = React.useState(items);
27 const [togglingGroup, setTogglingGroup] = React.useState(null);
28 
29 const pinned = listItems.filter((u) => u.pinned);
30 const unpinned = listItems.filter((u) => !u.pinned);
31 
32 const toggleStatus = (id) => {
33 const item = listItems.find((u) => u.id === id);
34 if (!item) return;
35 
36 setTogglingGroup(item.pinned ? 'pinned' : 'unpinned');
37 setListItems((prev) => {
38 const idx = prev.findIndex((u) => u.id === id);
39 if (idx === -1) return prev;
40 const updated = [...prev];
41 const [item] = updated.splice(idx, 1);
42 if (!item) return prev;
43 const toggled = { ...item, pinned: !item.pinned };
44 if (toggled.pinned) updated.push(toggled);
45 else updated.unshift(toggled);
46 return updated;
47 });
48 // Reset group z-index after the animation duration (keep in sync with animation timing)
49 setTimeout(() => setTogglingGroup(null), zIndexResetDelay);
50 };
51 
52 return (
53 <motion.div className={cn('space-y-10', className)} {...props}>
54 <LayoutGroup>
55 <div>
56 {pinned.length > 0 && (
57 <div
58 className={cn(
59 'space-y-3 relative',
60 togglingGroup === 'pinned' ? 'z-5' : 'z-10',
61 pinnedSectionClassName
62 )}>
63 {pinned.map((item) => (
64 <motion.div
65 key={item.id}
66 layoutId={`item-${item.id}`}
67 onClick={() => toggleStatus(item.id)}
68 transition={transition}
69 className="flex items-center justify-between gap-5 rounded-2xl bg-neutral-200 dark:bg-neutral-800 p-2">
70 <div className="flex items-center gap-2">
71 <div className="rounded-lg bg-background p-2">
72 <item.icon className="size-5 text-neutral-500 dark:text-neutral-400" />
73 </div>
74 <div>
75 <div className="text-sm font-semibold">{item.name}</div>
76 <div className="text-xs text-neutral-500 dark:text-neutral-400 font-medium">
77 {item.info}
78 </div>
79 </div>
80 </div>
81 <div
82 className="flex items-center justify-center size-8 rounded-full bg-neutral-400 dark:bg-neutral-600">
83 <Pin className="size-4 text-white fill-white" />
84 </div>
85 </motion.div>
86 ))}
87 </div>
88 )}
89 </div>
90 
91 <div>
92 <AnimatePresence>
93 {unpinned.length > 0 && (
94 <motion.p
95 layout
96 key="all-label"
97 className={cn(
98 'font-medium px-3 text-neutral-500 dark:text-neutral-300 text-sm mb-2',
99 labelClassName
100 )}
101 {...labelMotionProps}>
102 {labels.unpinned}
103 </motion.p>
104 )}
105 </AnimatePresence>
106 {unpinned.length > 0 && (
107 <div
108 className={cn(
109 'space-y-3 relative',
110 togglingGroup === 'unpinned' ? 'z-5' : 'z-10',
111 unpinnedSectionClassName
112 )}>
113 {unpinned.map((item) => (
114 <motion.div
115 key={item.id}
116 layoutId={`item-${item.id}`}
117 onClick={() => toggleStatus(item.id)}
118 transition={transition}
119 className="flex items-center justify-between gap-5 rounded-2xl bg-neutral-200 dark:bg-neutral-800 p-2 group">
120 <div className="flex items-center gap-2">
121 <div className="rounded-lg bg-background p-2">
122 <item.icon className="size-5 text-neutral-500 dark:text-neutral-400" />
123 </div>
124 <div>
125 <div className="text-sm font-semibold">{item.name}</div>
126 <div className="text-xs text-neutral-500 dark:text-neutral-400 font-medium">
127 {item.info}
128 </div>
129 </div>
130 </div>
131 <div
132 className="flex items-center justify-center size-8 rounded-full bg-neutral-400 dark:bg-neutral-600 opacity-0 group-hover:opacity-100 transition-opacity duration-250">
133 <Pin className="size-4 text-white" />
134 </div>
135 </motion.div>
136 ))}
137 </div>
138 )}
139 </div>
140 </LayoutGroup>
141 </motion.div>
142 );
143}
144 
145export { PinList };
146 

Uso

Props

PropTipoDefaultDescripción
itemsArray<{id, name, info, icon, pinned}>—Ítems de la lista.
labels{pinned, unpinned}…Etiquetas de sección.
transitionTransitionspringTransición de reordenamiento.

Acciones del componente

Search for a command to run...

Potenciá tu negocio con SFESS

Software a medida, IA integrada y ciberseguridad avanzada para empresas.

0años de ingeniería
0proyectos entregados
0% uptime
Servicios destacados
  • Consultoría SEO Avanzado & Analítica

    Auditoría técnica profunda para posicionar tu negocio en los primeros resultados de Google.

    SEO TécnicoGA4
  • Páginas Web Corporativas Complejas

    Plataformas empresariales masivas con múltiples secciones y animaciones fluidas.

    Framer MotionMulti-page
  • Integración de Inteligencia Artificial en Negocios

    Automatizamos la atención, las reservas y las ventas de tu negocio con agentes de IA.

    AI AgentsChatbots
Nuestro ecosistema
WORKENTA_OSSFESS_SOLUTIONSPRANALUZ_YOGAREMY_AI
Ver todos los servicios