[comunidad] · animate-ui / embla-carousel / motion
MotionCarousel
Carrusel con Embla y animaciones de Motion: dots animados tipo pill, escala del slide activo y navegación.
animate-uiembla-carouselmotion
@/components/animate-ui/components/community/motion-carouselMotionCarousel
1'use client';;2import * as React from 'react';3import { motion } from 'motion/react';4import useEmblaCarousel from 'embla-carousel-react';5import { Button } from '@/components/animate-ui/components/buttons/button';6import { ChevronRight, ChevronLeft } from 'lucide-react';7 8const transition = {9 type: 'spring',10 stiffness: 240,11 damping: 24,12 mass: 1,13};14 15const useEmblaControls = emblaApi => {16 const [selectedIndex, setSelectedIndex] = React.useState(0);17 const [scrollSnaps, setScrollSnaps] = React.useState([]);18 const [prevDisabled, setPrevDisabled] = React.useState(true);19 const [nextDisabled, setNextDisabled] = React.useState(true);20 21 const onDotClick = React.useCallback((index) => emblaApi?.scrollTo(index), [emblaApi]);22 23 const onPrev = React.useCallback(() => emblaApi?.scrollPrev(), [emblaApi]);24 const onNext = React.useCallback(() => emblaApi?.scrollNext(), [emblaApi]);25 26 const updateSelectionState = (api) => {27 setSelectedIndex(api.selectedScrollSnap());28 setPrevDisabled(!api.canScrollPrev());29 setNextDisabled(!api.canScrollNext());30 };31 32 const onInit = React.useCallback((api) => {33 setScrollSnaps(api.scrollSnapList());34 updateSelectionState(api);35 }, []);36 37 const onSelect = React.useCallback((api) => {38 updateSelectionState(api);39 }, []);40 41 React.useEffect(() => {42 if (!emblaApi) return;43 44 onInit(emblaApi);45 emblaApi.on('reInit', onInit).on('select', onSelect);46 47 return () => {48 emblaApi.off('reInit', onInit).off('select', onSelect);49 };50 }, [emblaApi, onInit, onSelect]);51 52 return {53 selectedIndex,54 scrollSnaps,55 prevDisabled,56 nextDisabled,57 onDotClick,58 onPrev,59 onNext,60 };61};62 63function MotionCarousel(props) {64 const { slides, options } = props;65 const [emblaRef, emblaApi] = useEmblaCarousel(options);66 const {67 selectedIndex,68 scrollSnaps,69 prevDisabled,70 nextDisabled,71 onDotClick,72 onPrev,73 onNext,74 } = useEmblaControls(emblaApi);75 76 return (77 <div78 className="w-full space-y-4 [--slide-height:9rem] sm:[--slide-height:13rem] md:[--slide-height:18rem] [--slide-spacing:1.5rem] [--slide-size:55%]">79 <div className="overflow-hidden" ref={emblaRef}>80 <div className="flex touch-pan-y touch-pinch-zoom">81 {slides.map((index) => {82 const isActive = index === selectedIndex;83 84 return (85 <motion.div86 key={index}87 className="h-[var(--slide-height)] mr-[var(--slide-spacing)] basis-[var(--slide-size)] flex-none flex min-w-0">88 <motion.div89 className="size-full flex items-center justify-center text-3xl md:text-5xl font-semibold select-none border-4 rounded-xl"90 initial={false}91 animate={{92 scale: isActive ? 1 : 0.9,93 }}94 transition={transition}>95 {index + 1}96 </motion.div>97 </motion.div>98 );99 })}100 </div>101 </div>102 103 <div className="flex justify-between">104 <Button size="icon" onClick={onPrev} disabled={prevDisabled}>105 <ChevronLeft className="size-5" />106 </Button>107 108 <div className="flex flex-wrap justify-end items-center gap-2">109 {scrollSnaps.map((_, index) => (110 <DotButton111 key={index}112 label={`Slide ${index + 1}`}113 selected={index === selectedIndex}114 onClick={() => onDotClick(index)} />115 ))}116 </div>117 118 <Button size="icon" onClick={onNext} disabled={nextDisabled}>119 <ChevronRight className="size-5" />120 </Button>121 </div>122 </div>123 );124}125 126function DotButton({127 selected = false,128 label,129 onClick130}) {131 return (132 <motion.button133 type="button"134 onClick={onClick}135 layout136 initial={false}137 className="flex cursor-pointer select-none items-center justify-center rounded-full border-none bg-primary text-primary-foreground text-sm"138 animate={{139 width: selected ? 68 : 12,140 height: selected ? 28 : 12,141 }}142 transition={transition}>143 <motion.span144 layout145 initial={false}146 className="block whitespace-nowrap px-3 py-1"147 animate={{148 opacity: selected ? 1 : 0,149 scale: selected ? 1 : 0,150 filter: selected ? 'blur(0)' : 'blur(4px)',151 }}152 transition={transition}>153 {label}154 </motion.span>155 </motion.button>156 );157}158 159export { MotionCarousel };160 Uso
Props
PropTipoDefaultDescripción
slidesReactNode[]—Contenido de cada slide.
optionsEmblaOptions—Opciones de Embla (loop, align…).
← Anterior
menosmás
[gráficas]HeatCalendar
Siguiente →RadialChart
Gráfica circular
Dock
Navegación flotante
All Items
PinList
Lista con fijados
CodeTabs
Tabs de código
SparklesText
Texto con destellos
[comunidad]PinList
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écnicoGA4Páginas Web Corporativas Complejas
Plataformas empresariales masivas con múltiples secciones y animaciones fluidas.
Framer MotionMulti-pageIntegració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
Ver todos los serviciosWORKENTA_OSSFESS_SOLUTIONSPRANALUZ_YOGAREMY_AI