shimmer.tsx 1.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. 'use client'
  2. import { cn } from '../../utils'
  3. import { motion } from 'motion/react'
  4. import {
  5. type CSSProperties,
  6. type ElementType,
  7. type JSX,
  8. memo,
  9. useMemo,
  10. } from 'react'
  11. export type TextShimmerProps = {
  12. children: string
  13. as?: ElementType
  14. className?: string
  15. duration?: number
  16. spread?: number
  17. }
  18. const ShimmerComponent = ({
  19. children,
  20. as: Component = 'p',
  21. className,
  22. duration = 2,
  23. spread = 2,
  24. }: TextShimmerProps) => {
  25. const MotionComponent = motion.create(
  26. Component as keyof JSX.IntrinsicElements
  27. )
  28. const dynamicSpread = useMemo(
  29. () => (children?.length ?? 0) * spread,
  30. [children, spread]
  31. )
  32. return (
  33. <MotionComponent
  34. animate={{ backgroundPosition: '0% center' }}
  35. className={cn(
  36. 'relative inline-block bg-[length:250%_100%,auto] bg-clip-text text-transparent',
  37. '[--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),var(--color-background),#0000_calc(50%+var(--spread)))] [background-repeat:no-repeat,padding-box]',
  38. className
  39. )}
  40. initial={{ backgroundPosition: '100% center' }}
  41. style={
  42. {
  43. '--spread': `${dynamicSpread}px`,
  44. backgroundImage:
  45. 'var(--bg), linear-gradient(var(--color-muted-foreground), var(--color-muted-foreground))',
  46. } as CSSProperties
  47. }
  48. transition={{
  49. repeat: Number.POSITIVE_INFINITY,
  50. duration,
  51. ease: 'linear',
  52. }}
  53. >
  54. {children}
  55. </MotionComponent>
  56. )
  57. }
  58. export const Shimmer = memo(ShimmerComponent)