{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "step-kit",
  "title": "Step Kit",
  "description": "Shared step parts: marker shapes, connectors, drawn check and travel timing for steppers.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "@ikas/motion",
    "@ikas/utils"
  ],
  "files": [
    {
      "path": "registry/ikas/step-kit.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { AnimatePresence, motion, useReducedMotion, type Transition } from \"motion/react\"\n\nimport { EASE_OUT, ICON_SPRING, INSTANT, SPRING, STAGGER } from \"@/lib/motion\"\nimport { cn } from \"@/lib/utils\"\n\ntype StepStatus = \"upcoming\" | \"current\" | \"done\" | \"error\" | \"blocked\"\ntype StepMarkerShape = \"diamond\" | \"circle\" | \"square\" | \"pill\"\n\nconst StepMarkerContext = React.createContext<StepMarkerShape | null>(null)\n\n/** The marker shape here: the marker's own `shape`, else the nearest StepMarkerProvider, else `diamond` (Launchpad's). */\nfunction useStepMarkerShape(shape?: StepMarkerShape): StepMarkerShape {\n  const inherited = React.useContext(StepMarkerContext)\n  return shape ?? inherited ?? \"diamond\"\n}\n\ntype StepMarkerProviderProps = {\n  /** Shape for every StepMarker, Steps, Launchpad and segmented ProgressBar inside. */\n  shape: StepMarkerShape\n  children?: React.ReactNode\n}\n\n/** Sets the step marker shape once for the app, so steppers and setup lists match. */\nfunction StepMarkerProvider({ shape, children }: StepMarkerProviderProps) {\n  return <StepMarkerContext.Provider value={shape}>{children}</StepMarkerContext.Provider>\n}\n\n/** Marker width in px per shape. Steps uses it to place connectors. */\nconst markerWidth = (shape: StepMarkerShape, size: \"default\" | \"sm\") => (size === \"sm\" ? (shape === \"pill\" ? 26 : 18) : shape === \"pill\" ? 36 : 26)\n\nconst shapes: Record<StepMarkerShape, { halo: string; disc: string }> = {\n  diamond: { halo: \"rotate-45 rounded-[7px]\", disc: \"inset-0.5 rotate-45 rounded-[5px]\" },\n  circle: { halo: \"rounded-full\", disc: \"inset-px rounded-full\" },\n  square: { halo: \"rounded-[9px]\", disc: \"inset-px rounded-md\" },\n  pill: { halo: \"rounded-full\", disc: \"inset-x-px inset-y-0.5 rounded-full\" },\n}\n\nconst discStatus: Record<StepStatus, string> = {\n  upcoming: \"bg-card shadow-[inset_0_0_0_1.5px_var(--border-strong)]\",\n  current: \"bg-card shadow-[inset_0_0_0_1.5px_var(--chart-ink)]\",\n  done: \"bg-chart-ink shadow-[inset_0_0_0_1.5px_var(--chart-ink)]\",\n  error: \"bg-[color-mix(in_oklab,var(--danger)_12%,var(--card))] shadow-[inset_0_0_0_1.5px_var(--danger)]\",\n  blocked: \"bg-card border-[1.5px] border-dashed border-border-strong\",\n}\n\nconst glyphStatus: Record<StepStatus, string> = {\n  upcoming: \"text-muted-foreground\",\n  current: \"text-foreground\",\n  done: \"text-card\",\n  error: \"text-danger\",\n  blocked: \"text-muted-foreground\",\n}\n\n/** A check that draws itself after `delay` seconds. */\nfunction CheckGlyph({ className, delay = 0 }: { className?: string; delay?: number }) {\n  const reduce = useReducedMotion()\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2.4} strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden className={cn(\"size-3.5\", className)}>\n      <motion.path\n        d=\"M5.5 12.5l4.25 4.25L18.5 8\"\n        initial={reduce ? false : { pathLength: 0 }}\n        animate={{ pathLength: 1 }}\n        transition={reduce ? INSTANT : { duration: 0.24, ease: EASE_OUT, delay: delay + 0.04 }}\n      />\n    </svg>\n  )\n}\n\n/** An exclamation mark: the stem draws, the dot pops after it. */\nfunction AlertGlyph({ className, delay = 0 }: { className?: string; delay?: number }) {\n  const reduce = useReducedMotion()\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2.4} strokeLinecap=\"round\" aria-hidden className={cn(\"size-3.5\", className)}>\n      <motion.path\n        d=\"M12 6.75v6.5\"\n        initial={reduce ? false : { pathLength: 0 }}\n        animate={{ pathLength: 1 }}\n        transition={reduce ? INSTANT : { duration: 0.24, ease: EASE_OUT, delay: delay + 0.04 }}\n      />\n      <motion.circle\n        cx=\"12\"\n        cy=\"17.4\"\n        r=\"1.3\"\n        fill=\"currentColor\"\n        stroke=\"none\"\n        initial={reduce ? false : { scale: 0.6, opacity: 0 }}\n        animate={{ scale: 1, opacity: 1 }}\n        transition={reduce ? INSTANT : { ...ICON_SPRING, delay: delay + 0.16 }}\n      />\n    </svg>\n  )\n}\n\n/** Icons swap in place: the old one shrinks away, the new one grows from 0.6 (never from 0) on ICON_SPRING. */\nfunction glyphMotion(reduce: boolean | null, delay: number) {\n  if (reduce) return { initial: { opacity: 0 }, animate: { opacity: 1, transition: { duration: 0.12 } }, exit: { opacity: 0, transition: { duration: 0.1 } } }\n  const fade: Transition = { duration: 0.16, ease: EASE_OUT, delay }\n  return {\n    initial: { opacity: 0, scale: 0.6, filter: \"blur(2px)\" },\n    animate: { opacity: 1, scale: 1, filter: \"blur(0px)\", transition: { scale: { ...ICON_SPRING, delay }, opacity: fade, filter: fade } },\n    exit: { opacity: 0, scale: 0.6, filter: \"blur(2px)\", transition: { duration: 0.12, ease: EASE_OUT } },\n  }\n}\n\ntype StepMarkerProps = Omit<React.ComponentProps<\"span\">, \"children\"> & {\n  status: StepStatus\n  /** Number shown while the step is not done. Hidden at `size=\"sm\"`. */\n  index?: number\n  /** diamond (default), circle, square or pill. Defaults to the StepMarkerProvider's. */\n  shape?: StepMarkerShape\n  /** sm is 18px: compact steppers and checklist boxes. */\n  size?: \"default\" | \"sm\"\n  /** Seconds to wait before the glyph and halo change, so a multi-step jump lands in order. */\n  delay?: number\n}\n\n/**\n * One step's marker. Done fills with ink and a check draws itself; the current step gets a halo\n * that grows behind it; an error turns red with an exclamation mark.\n */\nfunction StepMarker({ status, index, shape: shapeProp, size = \"default\", delay = 0, className, style, ...props }: StepMarkerProps) {\n  const reduce = useReducedMotion()\n  const shape = useStepMarkerShape(shapeProp)\n  const width = markerWidth(shape, size)\n  const height = size === \"sm\" ? 18 : 26\n  const halo = status === \"current\" || status === \"error\"\n  const glyph =\n    status === \"done\" ? <CheckGlyph delay={delay} className={size === \"sm\" ? \"size-3\" : undefined} />\n    : status === \"error\" ? <AlertGlyph delay={delay} className={size === \"sm\" ? \"size-3\" : undefined} />\n    : size === \"sm\" || index === undefined ? null\n    : <span className=\"font-mono text-[11px] font-semibold tabular-nums\">{index}</span>\n  const glyphKey = status === \"done\" ? \"done\" : status === \"error\" ? \"error\" : `n${size === \"sm\" ? \"\" : index}`\n\n  return (\n    <span\n      data-slot=\"step-marker\"\n      data-status={status}\n      data-shape={shape}\n      aria-hidden\n      className={cn(\"relative grid shrink-0 place-items-center\", className)}\n      style={{ width, height, ...style }}\n      {...props}\n    >\n      <AnimatePresence initial={false}>\n        {halo && (\n          <motion.span\n            key=\"halo\"\n            className={cn(\"absolute\", size === \"sm\" ? \"-inset-1\" : \"-inset-[5px]\", shapes[shape].halo, status === \"error\" ? \"bg-danger/15\" : \"bg-chart-ink/10\")}\n            {...(reduce\n              ? { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 } }\n              : {\n                  initial: { opacity: 0, scale: 0.6 },\n                  animate: { opacity: 1, scale: 1, transition: { scale: { ...ICON_SPRING, delay: delay + 0.1 }, opacity: { duration: 0.16, delay: delay + 0.1 } } },\n                  exit: { opacity: 0, scale: 0.6, transition: { duration: 0.12 } },\n                })}\n          />\n        )}\n      </AnimatePresence>\n      <span\n        className={cn(\n          \"absolute transition-[background-color,box-shadow,border-color] duration-200 ease-out motion-reduce:transition-none\",\n          size === \"sm\" && shape === \"diamond\" ? \"inset-[1.5px] rotate-45 rounded-[3.5px]\" : shapes[shape].disc,\n          discStatus[status]\n        )}\n      />\n      <span className={cn(\"relative grid place-items-center transition-colors duration-200 ease-out\", glyphStatus[status])}>\n        <AnimatePresence initial={false}>\n          {glyph && (\n            <motion.span key={glyphKey} className=\"col-start-1 row-start-1 grid place-items-center\" {...glyphMotion(reduce, delay)}>\n              {glyph}\n            </motion.span>\n          )}\n        </AnimatePresence>\n      </span>\n    </span>\n  )\n}\n\ntype StepConnectorProps = Omit<React.ComponentProps<\"span\">, \"children\"> & {\n  filled: boolean\n  orientation?: \"horizontal\" | \"vertical\"\n  /** Seconds to wait before filling or draining. */\n  delay?: number\n}\n\n/** The line between two steps. Fills from the done side on SPRING; drains back the same way. */\nfunction StepConnector({ filled, orientation = \"horizontal\", delay = 0, className, ...props }: StepConnectorProps) {\n  const reduce = useReducedMotion()\n  const horizontal = orientation === \"horizontal\"\n  return (\n    <span\n      data-slot=\"step-connector\"\n      aria-hidden\n      className={cn(\"relative overflow-hidden rounded-full bg-border-strong\", horizontal ? \"h-[1.5px]\" : \"w-[1.5px]\", className)}\n      {...props}\n    >\n      <motion.span\n        className={cn(\"absolute inset-0 bg-chart-ink\", horizontal ? \"origin-left\" : \"origin-top\")}\n        initial={false}\n        animate={horizontal ? { scaleX: filled ? 1 : 0 } : { scaleY: filled ? 1 : 0 }}\n        transition={reduce ? INSTANT : { ...SPRING, delay }}\n      />\n    </span>\n  )\n}\n\n/**\n * Remembers where `current` came from, so a jump over several steps fills one connector after another\n * (STAGGER apart) and drains from the far end when going back. The new current step's halo waits for\n * the last connector to start.\n */\nfunction useStepTravel(current: number) {\n  const [travel, setTravel] = React.useState({ from: current, to: current })\n  if (travel.to !== current) setTravel({ from: travel.to, to: current })\n  const { from, to } = travel\n  const forward = to >= from\n  return {\n    forward,\n    /** Delay in seconds for the change at index i (connector i, or step i). */\n    delay: (i: number) => Math.max(0, forward ? i - from : from - 1 - i) * STAGGER,\n    /** Delay in seconds for the marker that becomes current. */\n    landing: Math.max(0, Math.abs(to - from) - 1) * STAGGER,\n  }\n}\n\nexport {\n  AlertGlyph,\n  CheckGlyph,\n  StepConnector,\n  StepMarker,\n  StepMarkerProvider,\n  markerWidth,\n  useStepMarkerShape,\n  useStepTravel,\n  type StepConnectorProps,\n  type StepMarkerProps,\n  type StepMarkerProviderProps,\n  type StepMarkerShape,\n  type StepStatus,\n}\n",
      "type": "registry:component",
      "target": "@components/ikas/step-kit.tsx"
    }
  ],
  "docs": "https://builders.ikas.com/tr/docs/app-development/ui-kit/components/step-kit",
  "categories": [
    "motion",
    "utilities"
  ],
  "type": "registry:component"
}