{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "components-timeline",
  "title": "Timeline",
  "description": "A flexible scroll-driven timeline with animated progress, configurable orientations, customizable pins, and optional external scroll-container control.",
  "dependencies": [
    "motion",
    "class-variance-authority"
  ],
  "registryDependencies": [
    "@azemmur/hooks-use-is-mobile",
    "@azemmur/hooks-use-merged-refs"
  ],
  "files": [
    {
      "path": "registry/components/azemmur/timeline/index.tsx",
      "content": "// Copyright (c) 2026 raouf.codes - Azemmur\n\n'use client';\n\nimport type { Direction } from '@/components/azemmur/components/azemmur/timeline/timeline-context';\nimport { TimelineHeader } from '@/components/azemmur/components/azemmur/timeline/timeline-header';\nimport {\n  HorizontalTimeline,\n  type HorizontalTimelineProps,\n} from '@/components/azemmur/components/azemmur/timeline/timeline-horizontal';\nimport { TimelineItem } from '@/components/azemmur/components/azemmur/timeline/timeline-item';\nimport {\n  TimelineRoot,\n  type TimelineRootProps,\n} from '@/components/azemmur/components/azemmur/timeline/timeline-root';\nimport type {\n  TimelineVariantProps,\n  TimelineItemVariantProps,\n} from '@/components/azemmur/components/azemmur/timeline/timeline-variants';\nimport {\n  VerticalTimeline,\n  type VerticalTimelineProps,\n} from '@/components/azemmur/components/azemmur/timeline/timeline-vertical';\n\nconst Timeline = Object.assign(TimelineRoot, {\n  Header: TimelineHeader,\n  Item: TimelineItem,\n  Horizontal: HorizontalTimeline,\n  Vertical: VerticalTimeline,\n});\n\nexport {\n  Timeline,\n  TimelineRoot,\n  TimelineHeader,\n  TimelineItem,\n  HorizontalTimeline,\n  VerticalTimeline,\n};\nexport type {\n  Direction,\n  TimelineRootProps,\n  TimelineVariantProps,\n  TimelineItemVariantProps,\n  HorizontalTimelineProps,\n  VerticalTimelineProps,\n};\n",
      "type": "registry:ui",
      "target": "components/azemmur/timeline/index.tsx"
    },
    {
      "path": "registry/components/azemmur/timeline/timeline-root.tsx",
      "content": "// Copyright (c) 2026 raouf.codes - Azemmur\n\n'use client';\n\nimport { type ReactNode, type RefObject } from 'react';\nimport {\n  HorizontalTimeline,\n  type HorizontalTimelineProps,\n} from '@/components/azemmur/components/azemmur/timeline/timeline-horizontal';\nimport { type TimelineVariantProps } from '@/components/azemmur/components/azemmur/timeline/timeline-variants';\nimport {\n  VerticalTimeline,\n  type VerticalTimelineProps,\n} from '@/components/azemmur/components/azemmur/timeline/timeline-vertical';\nimport { useIsMobile } from '@/hooks/use-is-mobile';\n\ninterface TimelineRootProps extends TimelineVariantProps {\n  children: ReactNode;\n  className?: string;\n  progressClassName?: string;\n  ref?: RefObject<HTMLDivElement | null>;\n  containerScrollRef?: RefObject<HTMLElement | null>;\n  responsive?: boolean;\n  mobileBreakpoint?: number;\n  'aria-label'?: string;\n}\n\nfunction TimelineRoot({\n  children,\n  direction,\n  orientation = 'horizontal',\n  intent = 'primary',\n  size = 'md',\n  gradient = 'purple-blue',\n  visuals = 'none',\n  shape = 'circle',\n  className,\n  progressClassName,\n  ref,\n  containerScrollRef,\n  responsive = true,\n  mobileBreakpoint = 1024,\n  'aria-label': ariaLabel = 'Timeline',\n}: TimelineRootProps) {\n  const resolvedDirection = direction ?? 'ltr';\n  const isMobile = useIsMobile(mobileBreakpoint);\n  const resolvedOrientation =\n    responsive && orientation === 'horizontal' && isMobile\n      ? 'vertical'\n      : orientation;\n\n  const sharedProps = {\n    children,\n    direction: resolvedDirection,\n    intent,\n    size,\n    gradient,\n    visuals,\n    shape,\n    className,\n    progressClassName,\n    ref,\n    containerScrollRef,\n    'aria-label': ariaLabel,\n  };\n\n  if (isMobile == null) return null;\n\n  if (resolvedOrientation === 'vertical') {\n    return <VerticalTimeline {...sharedProps} />;\n  }\n\n  return <HorizontalTimeline {...sharedProps} />;\n}\n\nexport { TimelineRoot, type TimelineRootProps };\nexport type { HorizontalTimelineProps, VerticalTimelineProps };\n",
      "type": "registry:ui",
      "target": "components/azemmur/timeline/timeline-root.tsx"
    },
    {
      "path": "registry/components/azemmur/timeline/timeline-context.tsx",
      "content": "// Copyright (c) 2026 raouf.codes - Azemmur\n\n'use client';\n\nimport { MotionValue } from 'motion/react';\nimport { createContext, use } from 'react';\nimport type { TimelineVariantProps } from '@/components/azemmur/components/azemmur/timeline/timeline-variants';\n\ntype Direction = 'ltr' | 'rtl';\n\ninterface TimelineContextValue extends TimelineVariantProps {\n  scrollProgress: MotionValue<number>;\n  direction: Direction;\n  itemCount: number;\n  containerWidth: number;\n  containerHeight: number;\n  contentHeight: number;\n  pinWidth: number;\n  pinMargin: [number, number];\n  getTransformRange: (index: number) => number[][];\n}\n\nconst TimelineContext = createContext<TimelineContextValue | null>(null);\n\nfunction useTimeline() {\n  const context = use(TimelineContext);\n  if (!context) {\n    throw new Error('Timeline components must be used within <Timeline />');\n  }\n  return context;\n}\n\nexport {\n  TimelineContext,\n  useTimeline,\n  type TimelineContextValue,\n  type Direction,\n};\n",
      "type": "registry:ui",
      "target": "components/azemmur/timeline/timeline-context.tsx"
    },
    {
      "path": "registry/components/azemmur/timeline/timeline-header.tsx",
      "content": "// Copyright (c) 2026 raouf.codes - Azemmur\n\n'use client';\n\nimport { cn } from '@/lib/utils';\n\ninterface TimelineHeaderProps {\n  children: React.ReactNode;\n  className?: string;\n  orientation?: 'horizontal' | 'vertical';\n  direction?: 'ltr' | 'rtl';\n  __isTimelineHeader?: boolean;\n}\n\nfunction TimelineHeader({\n  children,\n  className,\n  orientation = 'horizontal',\n  direction,\n}: TimelineHeaderProps) {\n  const isHorizontal = orientation === 'horizontal';\n  const resolvedDirection = direction ?? 'ltr';\n\n  return (\n    <div\n      dir={resolvedDirection}\n      className={cn(\n        'flex items-center justify-center',\n        isHorizontal ? 'h-full' : 'mb-6',\n        className,\n      )}\n    >\n      {children}\n    </div>\n  );\n}\n\nTimelineHeader.__isTimelineHeader = true;\n\nexport { TimelineHeader, type TimelineHeaderProps };\n",
      "type": "registry:ui",
      "target": "components/azemmur/timeline/timeline-header.tsx"
    },
    {
      "path": "registry/components/azemmur/timeline/timeline-horizontal.tsx",
      "content": "// Copyright (c) 2026 raouf.codes - Azemmur\n\n'use client';\n\nimport { cn } from '@/lib/utils';\nimport { useScroll, useTransform, motion } from 'motion/react';\nimport {\n  useEffect,\n  useRef,\n  useState,\n  Children,\n  isValidElement,\n  cloneElement,\n  type ReactNode,\n  type ReactElement,\n  type RefObject,\n} from 'react';\nimport {\n  TimelineContext,\n  type Direction,\n} from '@/components/azemmur/components/azemmur/timeline/timeline-context';\nimport {\n  timelineVariants,\n  timelineProgressVariants,\n  type TimelineVariantProps,\n} from '@/components/azemmur/components/azemmur/timeline/timeline-variants';\nimport { useMergeRefs } from '@/hooks/use-merged-refs';\n\ninterface HorizontalTimelineProps extends TimelineVariantProps {\n  children: ReactNode;\n  className?: string;\n  progressClassName?: string;\n  ref?: RefObject<HTMLDivElement | null>;\n  containerScrollRef?: RefObject<HTMLElement | null>;\n  'aria-label'?: string;\n}\n\nfunction HorizontalTimeline({\n  children,\n  direction,\n  intent = 'primary',\n  size = 'md',\n  gradient = 'purple-blue',\n  visuals = 'none',\n  shape = 'circle',\n  className,\n  progressClassName,\n  ref,\n  containerScrollRef,\n  'aria-label': ariaLabel = 'Timeline',\n}: HorizontalTimelineProps) {\n  const resolvedDirection = direction ?? 'ltr';\n  const scrollContainerRef = useRef<HTMLDivElement>(null);\n  const timelineRowRef = useRef<HTMLDivElement>(null);\n  const headerRef = useRef<HTMLDivElement>(null);\n  const pinRef = useRef<HTMLDivElement>(null);\n\n  const combinedRef = useMergeRefs(scrollContainerRef, ref);\n  const scrollSourceRef = containerScrollRef ?? scrollContainerRef;\n\n  const [containerWidth, setContainerWidth] = useState(0);\n  const [containerHeight, setContainerHeight] = useState(0);\n  const [headerWidth, setHeaderWidth] = useState(0);\n  const [fullWidth, setFullWidth] = useState(0);\n  const [pinWidth, setPinWidth] = useState(0);\n  const [pinMargin, setPinMargin] = useState<[number, number]>([0, 0]);\n\n  const isRTL = resolvedDirection === 'rtl';\n  const orientation = 'horizontal' as const;\n\n  const { header, items } = (() => {\n    let headerElement: ReactNode = null;\n    const itemElements: ReactElement[] = [];\n\n    Children.forEach(children, (child) => {\n      if (isValidElement(child)) {\n        const childType = child.type as {\n          displayName?: string;\n          name?: string;\n          __isTimelineHeader?: boolean;\n          __isTimelineItem?: boolean;\n        };\n\n        if (childType.__isTimelineHeader) {\n          headerElement = child;\n        } else if (childType.__isTimelineItem) {\n          itemElements.push(child);\n        }\n      }\n    });\n\n    return { header: headerElement, items: itemElements };\n  })();\n\n  const itemCount = items.length;\n\n  useEffect(() => {\n    const updateSizes = () => {\n      if (!scrollContainerRef.current) return;\n\n      const rect = scrollContainerRef.current.getBoundingClientRect();\n      setContainerWidth(rect.width);\n      setContainerHeight(rect.height);\n\n      if (timelineRowRef.current) {\n        setFullWidth(timelineRowRef.current.getBoundingClientRect().width);\n      }\n\n      if (headerRef.current) {\n        setHeaderWidth(headerRef.current.getBoundingClientRect().width);\n      }\n\n      if (pinRef.current) {\n        const pinRect = pinRef.current.getBoundingClientRect();\n        setPinWidth(pinRect.width);\n        const styles = getComputedStyle(pinRef.current);\n        setPinMargin([\n          parseFloat(styles.marginLeft || '0'),\n          parseFloat(styles.marginRight || '0'),\n        ]);\n      }\n    };\n\n    updateSizes();\n\n    const resizeObserver = new ResizeObserver(updateSizes);\n    if (scrollContainerRef.current) {\n      resizeObserver.observe(scrollContainerRef.current);\n    }\n\n    return () => resizeObserver.disconnect();\n  }, []);\n\n  const { scrollYProgress } = useScroll({\n    container: scrollSourceRef,\n  });\n\n  const progressWidth = useTransform(\n    scrollYProgress,\n    [0, 1],\n    [headerWidth + pinWidth + 2 * pinMargin[1], containerWidth],\n  );\n\n  const progressOpacity = useTransform(scrollYProgress, [0, 0.1], [0, 1]);\n\n  const limit = isRTL\n    ? fullWidth - containerWidth\n    : -(fullWidth - containerWidth);\n\n  const scrollX = useTransform(scrollYProgress, [0, 1], [0, limit]);\n\n  const itemStops = Array.from(\n    { length: itemCount },\n    (_, i) => (containerWidth / itemCount) * i,\n  );\n\n  const getTransformRange = (index: number): number[][] => {\n    if (containerWidth === 0 || itemCount === 0) {\n      return [\n        [0, 1],\n        [0, 0],\n      ];\n    }\n\n    const startStop = itemStops[index] ?? 0;\n    const endStop =\n      index === itemCount - 1\n        ? containerWidth\n        : (itemStops[index + 1] ?? containerWidth);\n\n    const outputRange = isRTL\n      ? [-pinMargin[1], -(containerWidth - pinWidth - 3 * pinMargin[0])]\n      : [pinMargin[0], containerWidth - pinWidth - 3 * pinMargin[1]];\n\n    const inputRange = [startStop / containerWidth, endStop / containerWidth];\n\n    return [inputRange, outputRange];\n  };\n\n  const contextValue = {\n    scrollProgress: scrollYProgress,\n    direction: resolvedDirection,\n    itemCount,\n    containerWidth,\n    containerHeight,\n    contentHeight: 0,\n    pinWidth,\n    pinMargin,\n    getTransformRange,\n    intent,\n    orientation,\n    size,\n    gradient,\n    visuals,\n    shape,\n  };\n\n  const scrollHeight = containerWidth * itemCount;\n\n  const itemProps = {\n    intent,\n    orientation,\n    size,\n    visuals,\n    shape,\n    direction: resolvedDirection,\n  };\n\n  return (\n    <TimelineContext value={contextValue}>\n      <section\n        ref={combinedRef}\n        dir={resolvedDirection}\n        aria-label={ariaLabel}\n        className={cn(\n          timelineVariants({ orientation, intent, size }),\n          className,\n        )}\n      >\n        <div style={{ height: scrollHeight }}>\n          <div\n            className=\"sticky top-0 h-full\"\n            style={{ height: containerHeight }}\n          >\n            <div className=\"w-full bg-transparent\">\n              <div className=\"relative w-full\">\n                <motion.div\n                  aria-hidden=\"true\"\n                  className={cn(\n                    timelineProgressVariants({\n                      orientation,\n                      size,\n                      gradient,\n                      direction: resolvedDirection,\n                      intent,\n                    }),\n                    progressClassName,\n                  )}\n                  style={{\n                    width: progressWidth,\n                    opacity: progressOpacity,\n                  }}\n                />\n              </div>\n            </div>\n\n            <div className=\"h-full w-max flex flex-row\">\n              <motion.div\n                ref={timelineRowRef}\n                className=\"mx-auto py-3 flex flex-row h-full\"\n                style={{ x: scrollX }}\n              >\n                {header && (\n                  <div ref={headerRef}>\n                    {cloneElement(\n                      header as ReactElement<{\n                        orientation?: 'horizontal' | 'vertical';\n                        direction?: Direction;\n                      }>,\n                      {\n                        orientation:\n                          (\n                            header as ReactElement<{\n                              orientation?: 'horizontal' | 'vertical';\n                            }>\n                          ).props.orientation ?? orientation,\n                        direction:\n                          (\n                            header as ReactElement<{\n                              direction?: Direction;\n                            }>\n                          ).props.direction ?? resolvedDirection,\n                      },\n                    )}\n                  </div>\n                )}\n\n                <div role=\"list\" className=\"flex flex-row h-full\">\n                  {items.map((item, index) =>\n                    cloneElement(\n                      item as ReactElement<{\n                        index?: number;\n                        pinRef?: RefObject<HTMLDivElement | null>;\n                        scrollProgress?: typeof scrollYProgress;\n                        transformsRange?: number[][];\n                        [key: string]: unknown;\n                      }>,\n                      {\n                        key: index,\n                        index,\n                        ...itemProps,\n                        pinRef: index === 0 ? pinRef : undefined,\n                        scrollProgress: scrollYProgress,\n                        transformsRange: getTransformRange(index),\n                      },\n                    ),\n                  )}\n                </div>\n              </motion.div>\n            </div>\n          </div>\n        </div>\n      </section>\n    </TimelineContext>\n  );\n}\n\nexport { HorizontalTimeline, type HorizontalTimelineProps };\n",
      "type": "registry:ui",
      "target": "components/azemmur/timeline/timeline-horizontal.tsx"
    },
    {
      "path": "registry/components/azemmur/timeline/timeline-item.tsx",
      "content": "// Copyright (c) 2026 raouf.codes - Azemmur\n\n'use client';\n\nimport { cn } from '@/lib/utils';\nimport { motion, MotionValue, useTransform } from 'motion/react';\nimport type { ReactNode, RefObject } from 'react';\nimport { useTimeline } from '@/components/azemmur/components/azemmur/timeline/timeline-context';\nimport {\n  timelineItemVariants,\n  timelineContentVariants,\n  timelinePinVariants,\n  timelinePinDotVariants,\n  timelineTitleVariants,\n  type TimelineItemVariantProps,\n} from '@/components/azemmur/components/azemmur/timeline/timeline-variants';\n\ninterface TimelineItemProps extends TimelineItemVariantProps {\n  title: string;\n  children: ReactNode;\n  index?: number;\n  transformsRange?: number[][];\n  scrollProgress?: MotionValue<number>;\n  pinRef?: RefObject<HTMLDivElement | null>;\n  className?: string;\n  contentClassName?: string;\n  pinClassName?: string;\n  'aria-current'?: boolean | 'step' | 'true' | 'false';\n  __isTimelineItem?: boolean;\n}\n\nfunction TimelineItem({\n  title,\n  children,\n  index = 0,\n  transformsRange,\n  scrollProgress,\n  pinRef,\n  className,\n  contentClassName,\n  pinClassName,\n  direction: directionProp,\n  orientation: orientationProp,\n  intent: intentProp,\n  size: sizeProp,\n  visuals: visualsProp,\n  shape: shapeProp,\n  'aria-current': ariaCurrent,\n}: TimelineItemProps) {\n  const context = useTimeline();\n\n  const direction = directionProp ?? context.direction ?? 'ltr';\n  const orientation = orientationProp ?? context.orientation ?? 'horizontal';\n  const intent = intentProp ?? context.intent ?? 'primary';\n  const size = sizeProp ?? context.size ?? 'md';\n  const visuals = visualsProp ?? context.visuals ?? 'none';\n  const shape = shapeProp ?? context.shape ?? 'circle';\n\n  const isHorizontal = orientation === 'horizontal';\n  const { containerWidth } = context;\n\n  const x = useTransform(\n    scrollProgress || new MotionValue(0),\n    transformsRange?.[0] || [0, 1],\n    transformsRange?.[1] || [0, 0],\n  );\n\n  const titleId = `timeline-item-title-${index}`;\n\n  return (\n    <div\n      className={cn(timelineItemVariants({ orientation }), className)}\n      style={isHorizontal ? { width: containerWidth } : undefined}\n      role=\"listitem\"\n      aria-labelledby={titleId}\n      aria-current={ariaCurrent}\n      dir={direction}\n    >\n      <motion.div\n        ref={pinRef}\n        style={isHorizontal && scrollProgress ? { x } : undefined}\n        className={cn(\n          'flex z-10',\n          isHorizontal\n            ? 'flex-col self-start items-center w-fit mx-4'\n            : 'flex-col items-center shrink-0 self-start sticky top-0',\n          pinClassName,\n        )}\n      >\n        <div\n          aria-hidden=\"true\"\n          className={cn(timelinePinVariants({ size, shape, intent }))}\n        >\n          <div\n            className={cn(timelinePinDotVariants({ size, shape, intent }))}\n          />\n        </div>\n\n        {isHorizontal && (\n          <h3\n            id={titleId}\n            className={cn(timelineTitleVariants({ orientation, size, intent }))}\n          >\n            {title}\n          </h3>\n        )}\n      </motion.div>\n\n      <div\n        className={cn(\n          timelineContentVariants({ orientation }),\n          contentClassName,\n        )}\n      >\n        {!isHorizontal && (\n          <h3\n            id={titleId}\n            className={cn(timelineTitleVariants({ orientation, size, intent }))}\n          >\n            {title}\n          </h3>\n        )}\n        <div\n          className={cn(\n            timelineContentVariants({ orientation, visuals, intent }),\n            isHorizontal ? 'h-full' : '',\n          )}\n        >\n          {children}\n        </div>\n      </div>\n    </div>\n  );\n}\n\nTimelineItem.__isTimelineItem = true;\n\nexport { TimelineItem, type TimelineItemProps };\n",
      "type": "registry:ui",
      "target": "components/azemmur/timeline/timeline-item.tsx"
    },
    {
      "path": "registry/components/azemmur/timeline/timeline-variants.ts",
      "content": "// Copyright (c) 2026 raouf.codes - Azemmur\n\nimport { cva } from 'class-variance-authority';\n\nconst timelineVariants = cva(['relative w-full overflow-y-auto'], {\n  variants: {\n    orientation: {\n      horizontal: 'overflow-x-hidden',\n      vertical: 'overflow-x-visible px-4.5',\n    },\n    intent: {\n      primary: '',\n      accent: '',\n      secondary: '',\n      success: '',\n      info: '',\n      warning: '',\n      error: '',\n    },\n    size: {\n      sm: '',\n      md: '',\n      lg: '',\n    },\n  },\n  defaultVariants: {\n    orientation: 'horizontal',\n    intent: 'primary',\n    size: 'md',\n  },\n});\n\nconst timelineProgressVariants = cva(['absolute rounded-full'], {\n  variants: {\n    orientation: {\n      horizontal: 'start-0',\n      vertical: 'top-0',\n    },\n    size: {\n      sm: '',\n      md: '',\n      lg: '',\n    },\n    gradient: {\n      'purple-blue': 'from-purple-500 via-blue-500 to-transparent',\n      'orange-red': 'from-orange-500 via-red-500 to-transparent',\n      'green-teal': 'from-green-500 via-teal-500 to-transparent',\n      rainbow:\n        'from-red-500 via-yellow-500 via-green-500 via-blue-500 via-purple-500 to-transparent',\n      none: '',\n    },\n    direction: {\n      ltr: '',\n      rtl: '',\n    },\n    intent: {\n      primary: '',\n      accent: '',\n      secondary: '',\n      success: '',\n      info: '',\n      warning: '',\n      error: '',\n    },\n  },\n  compoundVariants: [\n    { orientation: 'horizontal', size: 'sm', className: 'h-[1px] top-6' },\n    { orientation: 'horizontal', size: 'md', className: 'h-[2px] top-7' },\n    { orientation: 'horizontal', size: 'lg', className: 'h-[3px] top-8' },\n    { orientation: 'vertical', size: 'sm', className: 'w-[1px] start-7' },\n    { orientation: 'vertical', size: 'md', className: 'w-[2px] start-8' },\n    { orientation: 'vertical', size: 'lg', className: 'w-[3px] start-9' },\n    {\n      orientation: 'horizontal',\n      direction: 'ltr',\n      gradient: ['purple-blue', 'orange-red', 'green-teal', 'rainbow'],\n      className: 'bg-gradient-to-r',\n    },\n    {\n      orientation: 'horizontal',\n      direction: 'rtl',\n      gradient: ['purple-blue', 'orange-red', 'green-teal', 'rainbow'],\n      className: 'bg-gradient-to-l',\n    },\n    {\n      orientation: 'vertical',\n      direction: ['ltr', 'rtl'],\n      gradient: ['purple-blue', 'orange-red', 'green-teal', 'rainbow'],\n      className: 'bg-gradient-to-b',\n    },\n    { gradient: 'none', intent: 'primary', className: 'bg-primary' },\n    { gradient: 'none', intent: 'accent', className: 'bg-accent' },\n    { gradient: 'none', intent: 'secondary', className: 'bg-secondary' },\n    { gradient: 'none', intent: 'success', className: 'bg-success' },\n    { gradient: 'none', intent: 'info', className: 'bg-info' },\n    { gradient: 'none', intent: 'warning', className: 'bg-warning' },\n    { gradient: 'none', intent: 'error', className: 'bg-error' },\n  ],\n  defaultVariants: {\n    orientation: 'horizontal',\n    size: 'md',\n    gradient: 'purple-blue',\n    direction: 'rtl',\n    intent: 'primary',\n  },\n});\n\nconst timelineItemVariants = cva(['flex justify-start'], {\n  variants: {\n    orientation: {\n      horizontal: 'flex-col w-full',\n      vertical: 'flex-row pt-4 pb-8',\n    },\n  },\n  defaultVariants: {\n    orientation: 'horizontal',\n  },\n});\n\nconst timelineContentVariants = cva(['relative w-full'], {\n  variants: {\n    orientation: {\n      horizontal: 'p-8 h-full content-center',\n      vertical: 'ps-6 pe-4 flex-1',\n    },\n    intent: {\n      primary: 'bg-primary border-primary',\n      accent: 'bg-accent border-accent',\n      secondary: 'bg-secondary border-secondary',\n      success: 'bg-success border-success',\n      info: 'bg-info border-info',\n      warning: 'bg-warning border-warning',\n      error: 'bg-error border-error',\n    },\n    visuals: {\n      card: 'bg-card shadow-md rounded-lg border border-border p-8',\n      bordered: 'border rounded-md p-8',\n      solid: 'rounded-md p-8',\n      dashed: 'border-2 border-dashed rounded-md p-8',\n      dotted: 'border-2 border-dotted rounded-md p-8',\n      none: '',\n    },\n  },\n  compoundVariants: [\n    {\n      visuals: ['bordered', 'dashed', 'dotted'],\n      intent: [\n        'primary',\n        'accent',\n        'secondary',\n        'success',\n        'info',\n        'warning',\n        'error',\n      ],\n      className: 'bg-transparent',\n    },\n    {\n      visuals: 'none',\n      intent: [\n        'primary',\n        'accent',\n        'secondary',\n        'success',\n        'info',\n        'warning',\n        'error',\n      ],\n      className: 'bg-transparent border-0 p-0',\n    },\n    {\n      visuals: 'solid',\n      intent: 'primary',\n      className: 'text-primary-foreground',\n    },\n    { visuals: 'solid', intent: 'accent', className: 'text-accent-foreground' },\n    {\n      visuals: 'solid',\n      intent: 'secondary',\n      className: 'text-secondary-foreground',\n    },\n    {\n      visuals: 'solid',\n      intent: 'success',\n      className: 'text-success-foreground',\n    },\n    {\n      visuals: 'solid',\n      intent: 'info',\n      className: 'text-info-foreground',\n    },\n    {\n      visuals: 'solid',\n      intent: 'warning',\n      className: 'text-warning-foreground',\n    },\n    {\n      visuals: 'solid',\n      intent: 'error',\n      className: 'text-error-foreground',\n    },\n  ],\n  defaultVariants: {\n    orientation: 'horizontal',\n    visuals: 'none',\n  },\n});\n\nconst timelinePinVariants = cva(['flex items-center justify-center shrink-0'], {\n  variants: {\n    size: {\n      sm: 'h-6 w-6',\n      md: 'h-8 w-8',\n      lg: 'h-10 w-10',\n    },\n    shape: {\n      circle: 'rounded-full',\n      ring: '',\n      dot: '',\n      square: 'rounded-sm',\n      diamond: 'rotate-45 rounded-sm',\n    },\n    intent: {\n      primary: 'bg-primary',\n      accent: 'bg-accent',\n      secondary: 'bg-secondary',\n      success: 'bg-success',\n      info: 'bg-info',\n      warning: 'bg-warning',\n      error: 'bg-error',\n    },\n  },\n  compoundVariants: [\n    {\n      shape: ['ring', 'dot'],\n      intent: [\n        'primary',\n        'accent',\n        'secondary',\n        'success',\n        'info',\n        'warning',\n        'error',\n      ],\n      className: 'bg-transparent',\n    },\n  ],\n  defaultVariants: {\n    size: 'md',\n    shape: 'circle',\n    intent: 'primary',\n  },\n});\n\nconst timelinePinDotVariants = cva(['border'], {\n  variants: {\n    size: {\n      sm: 'h-3 w-3',\n      md: 'h-4 w-4',\n      lg: 'h-5 w-5',\n    },\n    shape: {\n      circle: 'rounded-full',\n      ring: 'rounded-full',\n      dot: 'rounded-full border-0',\n      square: 'rounded-sm',\n      diamond: 'rounded-sm',\n    },\n    intent: {\n      primary: 'bg-primary-foreground border-primary',\n      accent: 'bg-accent-foreground border-accent',\n      secondary: 'bg-secondary-foreground border-secondary',\n      success: 'bg-success-foreground border-success',\n      info: 'bg-info-foreground border-info',\n      warning: 'bg-warning-foreground border-warning',\n      error: 'bg-error-foreground border-error',\n    },\n  },\n  compoundVariants: [\n    { shape: 'dot', intent: 'primary', className: 'bg-primary' },\n    { shape: 'dot', intent: 'accent', className: 'bg-accent' },\n    { shape: 'dot', intent: 'secondary', className: 'bg-secondary' },\n    { shape: 'dot', intent: 'success', className: 'bg-success' },\n    { shape: 'dot', intent: 'info', className: 'bg-info' },\n    { shape: 'dot', intent: 'warning', className: 'bg-warning' },\n    { shape: 'dot', intent: 'error', className: 'bg-error' },\n    {\n      shape: 'ring',\n      intent: [\n        'primary',\n        'accent',\n        'secondary',\n        'success',\n        'info',\n        'warning',\n        'error',\n      ],\n      className: 'bg-transparent ',\n    },\n    { shape: 'ring', size: 'sm', className: 'h-6 w-6 border-4' },\n    { shape: 'ring', size: 'md', className: 'h-7 w-7 border-5' },\n    { shape: 'ring', size: 'lg', className: 'h-8 w-8 border-6' },\n    { shape: 'diamond', intent: 'primary', className: 'bg-primary-foreground' },\n    { shape: 'diamond', intent: 'accent', className: 'bg-accent-foreground' },\n    {\n      shape: 'diamond',\n      intent: 'secondary',\n      className: 'bg-secondary-foreground',\n    },\n    { shape: 'diamond', intent: 'success', className: 'bg-success-foreground' },\n    { shape: 'diamond', intent: 'info', className: 'bg-info-foreground' },\n    { shape: 'diamond', intent: 'warning', className: 'bg-warning-foreground' },\n    { shape: 'diamond', intent: 'error', className: 'bg-error-foreground' },\n  ],\n  defaultVariants: {\n    size: 'md',\n    shape: 'circle',\n    intent: 'primary',\n  },\n});\n\nconst timelineTitleVariants = cva(['font-bold text-foreground'], {\n  variants: {\n    orientation: {\n      horizontal: 'mt-4',\n      vertical: 'mb-2 flex items-center',\n    },\n    intent: {\n      primary: 'text-primary',\n      accent: 'text-accent',\n      secondary: 'text-secondary',\n      success: 'text-success',\n      info: 'text-info',\n      warning: 'text-warning',\n      error: 'text-error',\n    },\n    size: {\n      sm: '',\n      md: '',\n      lg: '',\n    },\n  },\n  compoundVariants: [\n    { orientation: 'horizontal', size: 'sm', className: 'text-2xl' },\n    { orientation: 'horizontal', size: 'md', className: 'text-3xl' },\n    { orientation: 'horizontal', size: 'lg', className: 'text-4xl' },\n    {\n      orientation: 'vertical',\n      size: 'sm',\n      className: 'text-lg min-h-6',\n    },\n    {\n      orientation: 'vertical',\n      size: 'md',\n      className: 'text-xl min-h-8',\n    },\n    {\n      orientation: 'vertical',\n      size: 'lg',\n      className: 'text-2xl min-h-10',\n    },\n  ],\n  defaultVariants: {\n    orientation: 'horizontal',\n    size: 'md',\n  },\n});\n\ntype TimelineVariantProps = {\n  orientation?: 'horizontal' | 'vertical';\n  intent?:\n    | 'primary'\n    | 'accent'\n    | 'secondary'\n    | 'success'\n    | 'info'\n    | 'warning'\n    | 'error';\n  size?: 'sm' | 'md' | 'lg';\n  gradient?: 'purple-blue' | 'orange-red' | 'green-teal' | 'rainbow' | 'none';\n  direction?: 'ltr' | 'rtl';\n  visuals?: 'card' | 'bordered' | 'solid' | 'dashed' | 'dotted' | 'none';\n  shape?: 'circle' | 'ring' | 'dot' | 'square' | 'diamond';\n};\n\ntype TimelineItemVariantProps = Omit<TimelineVariantProps, 'gradient'>;\n\nexport {\n  timelineVariants,\n  timelineProgressVariants,\n  timelineItemVariants,\n  timelineContentVariants,\n  timelinePinVariants,\n  timelinePinDotVariants,\n  timelineTitleVariants,\n  type TimelineVariantProps,\n  type TimelineItemVariantProps,\n};\n",
      "type": "registry:ui",
      "target": "components/azemmur/timeline/timeline-variants.ts"
    },
    {
      "path": "registry/components/azemmur/timeline/timeline-vertical.tsx",
      "content": "// Copyright (c) 2026 raouf.codes - Azemmur\n\n'use client';\n\nimport { cn } from '@/lib/utils';\nimport { useScroll, useTransform, motion } from 'motion/react';\nimport {\n  useEffect,\n  useRef,\n  useState,\n  Children,\n  isValidElement,\n  cloneElement,\n  type ReactNode,\n  type ReactElement,\n  type RefObject,\n} from 'react';\nimport {\n  TimelineContext,\n  type Direction,\n} from '@/components/azemmur/components/azemmur/timeline/timeline-context';\nimport {\n  timelineVariants,\n  timelineProgressVariants,\n  type TimelineVariantProps,\n} from '@/components/azemmur/components/azemmur/timeline/timeline-variants';\nimport { useMergeRefs } from '@/hooks/use-merged-refs';\n\ninterface VerticalTimelineProps extends TimelineVariantProps {\n  children: ReactNode;\n  className?: string;\n  progressClassName?: string;\n  ref?: RefObject<HTMLDivElement | null>;\n  containerScrollRef?: RefObject<HTMLElement | null>;\n  'aria-label'?: string;\n}\n\nfunction VerticalTimeline({\n  children,\n  direction,\n  intent = 'primary',\n  size = 'md',\n  gradient = 'purple-blue',\n  visuals = 'none',\n  shape = 'circle',\n  className,\n  progressClassName,\n  ref,\n  containerScrollRef,\n  'aria-label': ariaLabel = 'Timeline',\n}: VerticalTimelineProps) {\n  const resolvedDirection = direction ?? 'ltr';\n  const scrollContainerRef = useRef<HTMLDivElement>(null);\n  const headerRef = useRef<HTMLDivElement>(null);\n  const pinRef = useRef<HTMLDivElement>(null);\n\n  const combinedRef = useMergeRefs(scrollContainerRef, ref);\n  const scrollSourceRef = containerScrollRef ?? scrollContainerRef;\n\n  const [containerHeight, setContainerHeight] = useState(0);\n  const [contentHeight, setContentHeight] = useState(0);\n  const [pinWidth, setPinWidth] = useState(0);\n  const [pinMargin, setPinMargin] = useState<[number, number]>([0, 0]);\n\n  const orientation = 'vertical' as const;\n\n  const { header, items } = (() => {\n    let headerElement: ReactNode = null;\n    const itemElements: ReactElement[] = [];\n\n    Children.forEach(children, (child) => {\n      if (isValidElement(child)) {\n        const childType = child.type as {\n          displayName?: string;\n          name?: string;\n          __isTimelineHeader?: boolean;\n          __isTimelineItem?: boolean;\n        };\n\n        if (childType.__isTimelineHeader) {\n          headerElement = child;\n        } else if (childType.__isTimelineItem) {\n          itemElements.push(child);\n        }\n      }\n    });\n\n    return { header: headerElement, items: itemElements };\n  })();\n\n  const itemCount = items.length;\n\n  useEffect(() => {\n    const updateSizes = () => {\n      if (!scrollContainerRef.current) return;\n\n      const rect = scrollContainerRef.current.getBoundingClientRect();\n      setContainerHeight(rect.height);\n      setContentHeight(scrollContainerRef.current.scrollHeight);\n\n      if (pinRef.current) {\n        const pinRect = pinRef.current.getBoundingClientRect();\n        setPinWidth(pinRect.width);\n        const styles = getComputedStyle(pinRef.current);\n        setPinMargin([\n          parseFloat(styles.marginLeft || '0'),\n          parseFloat(styles.marginRight || '0'),\n        ]);\n      }\n    };\n\n    updateSizes();\n\n    const resizeObserver = new ResizeObserver(updateSizes);\n    if (scrollContainerRef.current) {\n      resizeObserver.observe(scrollContainerRef.current);\n    }\n\n    return () => resizeObserver.disconnect();\n  }, []);\n\n  const { scrollYProgress } = useScroll({\n    container: scrollSourceRef,\n  });\n\n  const progressHeight = useTransform(\n    scrollYProgress,\n    [0, 1],\n    [0, Math.max(contentHeight, containerHeight)],\n  );\n\n  const progressOpacity = useTransform(scrollYProgress, [0, 0.1], [0, 1]);\n\n  const getTransformRange = (_index: number): number[][] => {\n    return [\n      [0, 1],\n      [0, 0],\n    ];\n  };\n\n  const contextValue = {\n    scrollProgress: scrollYProgress,\n    direction: resolvedDirection,\n    itemCount,\n    containerWidth: 0,\n    containerHeight,\n    contentHeight,\n    pinWidth,\n    pinMargin,\n    getTransformRange,\n    intent,\n    orientation,\n    size,\n    gradient,\n    visuals,\n    shape,\n  };\n\n  const itemProps = {\n    intent,\n    orientation,\n    size,\n    visuals,\n    shape,\n    direction: resolvedDirection,\n  };\n\n  return (\n    <TimelineContext value={contextValue}>\n      <section\n        ref={combinedRef}\n        dir={resolvedDirection}\n        aria-label={ariaLabel}\n        className={cn(\n          timelineVariants({ orientation, intent, size }),\n          'relative',\n          containerScrollRef ? 'overflow-y-visible' : '',\n          className,\n        )}\n      >\n        <motion.div\n          aria-hidden=\"true\"\n          className={cn(\n            timelineProgressVariants({\n              orientation,\n              size,\n              gradient,\n              direction: resolvedDirection,\n              intent,\n            }),\n            progressClassName,\n          )}\n          style={{\n            height: progressHeight,\n            opacity: progressOpacity,\n          }}\n        />\n\n        <div className=\"h-full\">\n          {header && (\n            <div ref={headerRef} className=\"mb-4\">\n              {cloneElement(\n                header as ReactElement<{\n                  orientation?: 'horizontal' | 'vertical';\n                  direction?: Direction;\n                }>,\n                {\n                  orientation:\n                    (\n                      header as ReactElement<{\n                        orientation?: 'horizontal' | 'vertical';\n                      }>\n                    ).props.orientation ?? orientation,\n                  direction:\n                    (\n                      header as ReactElement<{\n                        direction?: Direction;\n                        dir?: Direction;\n                      }>\n                    ).props.direction ?? resolvedDirection,\n                },\n              )}\n            </div>\n          )}\n\n          <div role=\"list\" className=\"flex flex-col\">\n            {items.map((item, index) =>\n              cloneElement(\n                item as ReactElement<{\n                  index?: number;\n                  pinRef?: RefObject<HTMLDivElement | null>;\n                  scrollProgress?: typeof scrollYProgress;\n                  [key: string]: unknown;\n                }>,\n                {\n                  key: index,\n                  index,\n                  ...itemProps,\n                  pinRef: index === 0 ? pinRef : undefined,\n                  scrollProgress: scrollYProgress,\n                },\n              ),\n            )}\n          </div>\n        </div>\n      </section>\n    </TimelineContext>\n  );\n}\n\nexport { VerticalTimeline, type VerticalTimelineProps };\n",
      "type": "registry:ui",
      "target": "components/azemmur/timeline/timeline-vertical.tsx"
    }
  ],
  "type": "registry:ui"
}