@dxkit-org/react-marquee
Version:
A modern, lightweight React marquee component with TypeScript support. Features customizable animations, fade effects, direction control, and accessibility-friendly scrolling text.
1 lines • 12 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.tsx","#style-inject:#style-inject","../src/global.css","../src/lib/utils.ts"],"sourcesContent":["import { HTMLAttributes, ReactNode, useLayoutEffect, useState } from \"react\";\r\nimport \"./global.css\";\r\nimport { cn } from \"./lib/utils\";\r\n\r\n/**\r\n * Props for the Marquee component\r\n * \r\n * @public\r\n */\r\nexport type MarqueeProps = {\r\n /** Content to display in the marquee animation */\r\n children: ReactNode;\r\n /** Direction of the marquee animation */\r\n direction?: \"left\" | \"right\" | \"up\" | \"down\";\r\n /** Whether to reverse the animation direction */\r\n reverse?: boolean;\r\n /** Whether to pause the animation when user hovers over the component */\r\n pauseOnHover?: boolean;\r\n\r\n /** Whether to apply a fade effect at the edges of the marquee */\r\n fade?: boolean;\r\n /** Number of copies of the content to create for seamless looping */\r\n numberOfCopies?: number;\r\n /** Additional props to pass to the outer container div */\r\n containerProps?: HTMLAttributes<HTMLDivElement>;\r\n /** Additional props to pass to the content wrapper div */\r\n childrenWrapperProps?: HTMLAttributes<HTMLDivElement>;\r\n};\r\n\r\n/**\r\n * A modern, lightweight React marquee component with full TypeScript support.\r\n * \r\n * Perfect for creating smooth scrolling animations, news tickers, logo carousels,\r\n * and any other horizontal or vertical scrolling content in your React applications.\r\n * \r\n * Features:\r\n * - Four-directional scrolling (left, right, up, down)\r\n * - Fade effects with gradient masks\r\n * - Pause on hover functionality\r\n * - Reverse animation direction\r\n * - Configurable content repetition\r\n * - Full accessibility support\r\n * - Zero external dependencies\r\n * - TypeScript first with complete type definitions\r\n * \r\n * @example\r\n * ```tsx\r\n * // Basic horizontal marquee (left to right)\r\n * <Marquee>\r\n * <span>Welcome to our amazing React Marquee component!</span>\r\n * </Marquee>\r\n * \r\n * // Right to left horizontal marquee\r\n * <Marquee direction=\"right\">\r\n * <span>This text scrolls from left to right!</span>\r\n * </Marquee>\r\n * \r\n * // Vertical marquee with fade effect and pause on hover\r\n * <Marquee direction=\"up\" fade pauseOnHover>\r\n * <div>Latest news item 1</div>\r\n * <div>Latest news item 2</div>\r\n * <div>Latest news item 3</div>\r\n * </Marquee>\r\n * \r\n * // Downward vertical marquee\r\n * <Marquee direction=\"down\" fade>\r\n * <div>Scrolling down content</div>\r\n * </Marquee>\r\n * \r\n * // Logo carousel with custom styling\r\n * <Marquee \r\n * direction=\"left\"\r\n * pauseOnHover \r\n * numberOfCopies={3}\r\n * containerProps={{ className: \"bg-gray-100 p-4\" }}\r\n * >\r\n * <div className=\"flex items-center gap-8\">\r\n * <img src=\"/logo1.png\" alt=\"Company 1\" />\r\n * <img src=\"/logo2.png\" alt=\"Company 2\" />\r\n * </div>\r\n * </Marquee>\r\n * ```\r\n * \r\n * @param props - Configuration options for the marquee component\r\n * @returns A React functional component that renders an animated marquee\r\n * \r\n * @public\r\n */\r\n\r\nexport function Marquee({\r\n children,\r\n direction = \"left\",\r\n pauseOnHover = false,\r\n reverse = false,\r\n fade = false,\r\n\r\n numberOfCopies = 2,\r\n containerProps,\r\n childrenWrapperProps\r\n}: MarqueeProps) {\r\n const { className, ...rest } = containerProps || {};\r\n const { className: innerClassName, ...childrenWrapperRest } = childrenWrapperProps || {};\r\n\r\n // State to track when component is fully mounted and ready\r\n const [actualCopies, setActualCopies] = useState(1);\r\n // Use layout effect to set ready state before browser paint\r\n useLayoutEffect(() => {\r\n const timer = setTimeout(() => {\r\n setActualCopies(numberOfCopies);\r\n }, 0); // Use setTimeout to ensure this runs after the initial render\r\n\r\n return () => clearTimeout(timer);\r\n }, []);\r\n\r\n const isHorizontal = direction === \"left\" || direction === \"right\";\r\n\r\n // Use only 1 copy until component is ready, then use the specified numberOfCopies\r\n\r\n return (\r\n <div\r\n className={cn(\r\n \"group dxkit-marquee-flex dxkit-marquee-gap-[1rem] dxkit-marquee-overflow-hidden\",\r\n isHorizontal ? \"dxkit-marquee-flex-row\" : \"dxkit-marquee-flex-col\",\r\n className\r\n )}\r\n style={{\r\n maskImage: fade\r\n ? `linear-gradient(${isHorizontal ? \"to right\" : \"to bottom\"\r\n }, transparent 0%, rgba(0, 0, 0, 1.0) 10%, rgba(0, 0, 0, 1.0) 90%, transparent 100%)`\r\n : undefined,\r\n WebkitMaskImage: fade\r\n ? `linear-gradient(${isHorizontal ? \"to right\" : \"to bottom\"\r\n }, transparent 0%, rgba(0, 0, 0, 1.0) 10%, rgba(0, 0, 0, 1.0) 90%, transparent 100%)`\r\n : undefined,\r\n }}\r\n {...rest}\r\n >\r\n {Array(actualCopies)\r\n .fill(0)\r\n .map((_, i) => (\r\n <div\r\n key={i}\r\n className={cn(\r\n \"dxkit-marquee-flex dxkit-marquee-justify-around dxkit-marquee-gap-[1rem] [--gap:1rem] dxkit-marquee-shrink-0\",\r\n isHorizontal\r\n ? \"dxkit-marquee-animate-marquee-left dxkit-marquee-flex-row\"\r\n : \"dxkit-marquee-animate-marquee-up dxkit-marquee-flex-col\",\r\n pauseOnHover && \"dxkit-marquee-group-hover:dxkit-marquee-animation-paused\",\r\n (reverse || direction === \"right\" || direction === \"down\") && \"dxkit-marquee-direction-reverse\",\r\n innerClassName\r\n )}\r\n {...childrenWrapperRest}\r\n >\r\n {children}\r\n </div>\r\n ))}\r\n </div>\r\n );\r\n}","\n export default function styleInject(css, { insertAt } = {}) {\n if (!css || typeof document === 'undefined') return\n \n const head = document.head || document.getElementsByTagName('head')[0]\n const style = document.createElement('style')\n style.type = 'text/css'\n \n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild)\n } else {\n head.appendChild(style)\n }\n } else {\n head.appendChild(style)\n }\n \n if (style.styleSheet) {\n style.styleSheet.cssText = css\n } else {\n style.appendChild(document.createTextNode(css))\n }\n }\n ","import styleInject from '#style-inject';styleInject(\".dxkit-marquee-flex {\\n display: flex;\\n}\\n.dxkit-marquee-shrink-0 {\\n flex-shrink: 0;\\n}\\n@keyframes dxkit-marquee-marquee-left {\\n from {\\n transform: translateX(0);\\n }\\n to {\\n transform: translateX(calc(-100% - var(--gap)));\\n }\\n}\\n.dxkit-marquee-animate-marquee-left {\\n animation: dxkit-marquee-marquee-left var(--duration, 40s) linear infinite;\\n}\\n@keyframes dxkit-marquee-marquee-up {\\n from {\\n transform: translateY(0);\\n }\\n to {\\n transform: translateY(calc(-100% - var(--gap)));\\n }\\n}\\n.dxkit-marquee-animate-marquee-up {\\n animation: dxkit-marquee-marquee-up var(--duration, 40s) linear infinite;\\n}\\n.dxkit-marquee-flex-row {\\n flex-direction: row;\\n}\\n.dxkit-marquee-flex-col {\\n flex-direction: column;\\n}\\n.dxkit-marquee-justify-around {\\n justify-content: space-around;\\n}\\n.dxkit-marquee-gap-\\\\[1rem\\\\] {\\n gap: 1rem;\\n}\\n.dxkit-marquee-overflow-hidden {\\n overflow: hidden;\\n}\\n.group:hover .dxkit-marquee-group-hover\\\\:dxkit-marquee-animation-paused {\\n animation-play-state: paused;\\n}\\n.dxkit-marquee-direction-reverse {\\n animation-direction: reverse;\\n}\\n.dxkit-marquee-animate-marquee-left {\\n animation: marquee-left var(--marquee-duration, 15s) linear infinite;\\n}\\n.dxkit-marquee-animate-marquee-up {\\n animation: marquee-up var(--marquee-duration, 15s) linear infinite;\\n}\\n.dxkit-marquee-flex {\\n display: flex;\\n}\\n.dxkit-marquee-flex-row {\\n flex-direction: row;\\n}\\n.dxkit-marquee-flex-col {\\n flex-direction: column;\\n}\\n.dxkit-marquee-gap-\\\\[1rem\\\\] {\\n gap: 1rem;\\n}\\n.dxkit-marquee-overflow-hidden {\\n overflow: hidden;\\n}\\n.dxkit-marquee-justify-around {\\n justify-content: space-around;\\n}\\n.dxkit-marquee-shrink-0 {\\n flex-shrink: 0;\\n}\\n.\\\\[--gap\\\\:1rem\\\\] {\\n --gap: 1rem;\\n}\\n@keyframes marquee-left {\\n 0% {\\n transform: translateX(0%);\\n }\\n 100% {\\n transform: translateX(-100%);\\n }\\n}\\n@keyframes marquee-up {\\n 0% {\\n transform: translateY(0%);\\n }\\n 100% {\\n transform: translateY(-100%);\\n }\\n}\\n.dxkit-marquee-flex,\\n.dxkit-marquee-animate-marquee-left,\\n.dxkit-marquee-animate-marquee-up {\\n will-change: transform;\\n transform: translateZ(0);\\n}\\n@media (prefers-reduced-motion: reduce) {\\n .dxkit-marquee-animate-marquee-left,\\n .dxkit-marquee-animate-marquee-up {\\n animation-duration: 30s;\\n }\\n}\\n@supports (transform: translate3d(0, 0, 0)) {\\n .dxkit-marquee-animate-marquee-left {\\n animation-name: marquee-left-3d;\\n }\\n .dxkit-marquee-animate-marquee-up {\\n animation-name: marquee-up-3d;\\n }\\n}\\n@keyframes marquee-left-3d {\\n 0% {\\n transform: translate3d(0%, 0, 0);\\n }\\n 100% {\\n transform: translate3d(-100%, 0, 0);\\n }\\n}\\n@keyframes marquee-up-3d {\\n 0% {\\n transform: translate3d(0, 0%, 0);\\n }\\n 100% {\\n transform: translate3d(0, -100%, 0);\\n }\\n}\\n\")","import { clsx, type ClassValue } from \"clsx\"\r\nimport { twMerge } from \"tailwind-merge\"\r\n\r\nexport function cn(...inputs: ClassValue[]) {\r\n return twMerge(clsx(inputs))\r\n}\r\n"],"mappings":";AAAA,SAAoC,iBAAiB,gBAAgB;;;ACC5C,SAAR,YAA6B,KAAK,EAAE,SAAS,IAAI,CAAC,GAAG;AAC1D,MAAI,CAAC,OAAO,OAAO,aAAa,YAAa;AAE7C,QAAM,OAAO,SAAS,QAAQ,SAAS,qBAAqB,MAAM,EAAE,CAAC;AACrE,QAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,QAAM,OAAO;AAEb,MAAI,aAAa,OAAO;AACtB,QAAI,KAAK,YAAY;AACnB,WAAK,aAAa,OAAO,KAAK,UAAU;AAAA,IAC1C,OAAO;AACL,WAAK,YAAY,KAAK;AAAA,IACxB;AAAA,EACF,OAAO;AACL,SAAK,YAAY,KAAK;AAAA,EACxB;AAEA,MAAI,MAAM,YAAY;AACpB,UAAM,WAAW,UAAU;AAAA,EAC7B,OAAO;AACL,UAAM,YAAY,SAAS,eAAe,GAAG,CAAC;AAAA,EAChD;AACF;;;ACvB8B,YAAY,4wFAA4wF;;;ACAh0F,SAAS,YAA6B;AACtC,SAAS,eAAe;AAEjB,SAAS,MAAM,QAAsB;AAC1C,SAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;;;AHuIoB;AAnDb,SAAS,QAAQ;AAAA,EACpB;AAAA,EACA,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,UAAU;AAAA,EACV,OAAO;AAAA,EAEP,iBAAiB;AAAA,EACjB;AAAA,EACA;AACJ,GAAiB;AACb,QAAM,EAAE,WAAW,GAAG,KAAK,IAAI,kBAAkB,CAAC;AAClD,QAAM,EAAE,WAAW,gBAAgB,GAAG,oBAAoB,IAAI,wBAAwB,CAAC;AAGvF,QAAM,CAAC,cAAc,eAAe,IAAI,SAAS,CAAC;AAElD,kBAAgB,MAAM;AAClB,UAAM,QAAQ,WAAW,MAAM;AAC3B,sBAAgB,cAAc;AAAA,IAClC,GAAG,CAAC;AAEJ,WAAO,MAAM,aAAa,KAAK;AAAA,EACnC,GAAG,CAAC,CAAC;AAEL,QAAM,eAAe,cAAc,UAAU,cAAc;AAI3D,SACI;AAAA,IAAC;AAAA;AAAA,MACG,WAAW;AAAA,QACP;AAAA,QACA,eAAe,2BAA2B;AAAA,QAC1C;AAAA,MACJ;AAAA,MACA,OAAO;AAAA,QACH,WAAW,OACL,mBAAmB,eAAe,aAAa,WACjD,wFACE;AAAA,QACN,iBAAiB,OACX,mBAAmB,eAAe,aAAa,WACjD,wFACE;AAAA,MACV;AAAA,MACC,GAAG;AAAA,MAEH,gBAAM,YAAY,EACd,KAAK,CAAC,EACN,IAAI,CAAC,GAAG,MACL;AAAA,QAAC;AAAA;AAAA,UAEG,WAAW;AAAA,YACP;AAAA,YACA,eACM,8DACA;AAAA,YACN,gBAAgB;AAAA,aACf,WAAW,cAAc,WAAW,cAAc,WAAW;AAAA,YAC9D;AAAA,UACJ;AAAA,UACC,GAAG;AAAA,UAEH;AAAA;AAAA,QAZI;AAAA,MAaT,CACH;AAAA;AAAA,EACT;AAER;","names":[]}