{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "button-61",
  "title": "Download with progress",
  "description": "The button fills with the download progress, counts the percentage and ends on a check.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://ui.ndekocode.dev/r/animated-size.json",
    "https://ui.ndekocode.dev/r/button.json",
    "https://ui.ndekocode.dev/r/icons.json",
    "https://ui.ndekocode.dev/r/theme.json",
    "https://ui.ndekocode.dev/r/use-prefers-reduced-motion.json"
  ],
  "files": [
    {
      "path": "registry/ndeko/components/button/button-61.tsx",
      "content": "'use client';\n\nimport {\n  AnimatePresence,\n  motion,\n  type Transition,\n  type Variants,\n} from 'motion/react';\nimport { useEffect, useState } from 'react';\n\nimport { usePrefersReducedMotion } from '@/registry/ndeko/hooks/use-prefers-reduced-motion';\nimport { CheckIcon, DownloadIcon } from '@/registry/ndeko/lib/icons';\nimport { AnimatedSize } from '@/registry/ndeko/ui/animated-size';\nimport { Button } from '@/registry/ndeko/ui/button';\n\ntype DownloadStatus = 'idle' | 'downloading' | 'done';\n\nconst PROGRESS_STEPS = [6, 14, 27, 38, 46, 59, 71, 80, 88, 96, 100];\nconst COMPLETE = 100;\nconst STEP_MS = 180;\nconst SETTLE_MS = 220;\nconst DONE_RESET_MS = 2200;\n\nconst FILL =\n  'bg-primary/12 pointer-events-none absolute inset-0 -z-10 transition-[translate,opacity] duration-180 ease-linear';\n\nconst SWAP_TRANSITION: Transition = { duration: 0.15, ease: [0.2, 0, 0, 1] };\n\nconst ICON_VARIANTS: Variants = {\n  enter: { opacity: 0, scale: 0.6, filter: 'blur(2px)' },\n  visible: { opacity: 1, scale: 1, filter: 'blur(0px)' },\n  exit: { opacity: 0, scale: 0.6, filter: 'blur(2px)' },\n};\n\nconst LABEL_VARIANTS: Variants = {\n  enter: { opacity: 0, filter: 'blur(2px)' },\n  visible: { opacity: 1, filter: 'blur(0px)' },\n  exit: { opacity: 0, filter: 'blur(2px)' },\n};\n\nconst FADE_VARIANTS: Variants = {\n  enter: { opacity: 0 },\n  visible: { opacity: 1 },\n  exit: { opacity: 0 },\n};\n\nfunction iconVariantsOf(shouldReduceMotion: boolean | null) {\n  if (shouldReduceMotion) {\n    return FADE_VARIANTS;\n  }\n  return ICON_VARIANTS;\n}\n\nfunction labelVariantsOf(shouldReduceMotion: boolean | null) {\n  if (shouldReduceMotion) {\n    return FADE_VARIANTS;\n  }\n  return LABEL_VARIANTS;\n}\n\nfunction nextProgressOf(progress: number) {\n  const nextStep = PROGRESS_STEPS.find((step) => step > progress);\n  if (nextStep === undefined) {\n    return COMPLETE;\n  }\n  return nextStep;\n}\n\nfunction iconOf(status: DownloadStatus) {\n  if (status === 'done') {\n    return <CheckIcon />;\n  }\n  return <DownloadIcon className='opacity-60' />;\n}\n\nfunction DownloadLabel({\n  status,\n  progress,\n}: {\n  status: DownloadStatus;\n  progress: number;\n}) {\n  if (status === 'downloading') {\n    return (\n      <>\n        Downloading\n        <span className='ms-1 inline-block w-[4ch] text-end tabular-nums'>\n          {progress}%\n        </span>\n      </>\n    );\n  }\n  if (status === 'done') {\n    return 'Downloaded';\n  }\n  return 'Download report';\n}\n\nfunction announcementOf(status: DownloadStatus) {\n  if (status === 'downloading') {\n    return 'Downloading the report';\n  }\n  if (status === 'done') {\n    return 'Report downloaded';\n  }\n  return '';\n}\n\nfunction fillStyleOf(\n  status: DownloadStatus,\n  progress: number\n): React.CSSProperties {\n  const offset = progress - COMPLETE;\n  const isDownloading = status === 'downloading';\n  return { translate: `${offset}% 0`, opacity: Number(isDownloading) };\n}\n\nexport default function Button61() {\n  const [status, setStatus] = useState<DownloadStatus>('idle');\n  const [progress, setProgress] = useState(0);\n  const shouldReduceMotion = usePrefersReducedMotion();\n  const iconVariants = iconVariantsOf(shouldReduceMotion);\n  const labelVariants = labelVariantsOf(shouldReduceMotion);\n  const isDownloading = status === 'downloading';\n  const isDone = status === 'done';\n  const isBusy = status !== 'idle';\n  const hasReachedEnd = progress === COMPLETE;\n  const iconKey = String(isDone);\n\n  useEffect(() => {\n    if (!isDownloading) {\n      return;\n    }\n    const interval = window.setInterval(() => {\n      setProgress(nextProgressOf);\n    }, STEP_MS);\n    return () => window.clearInterval(interval);\n  }, [isDownloading]);\n\n  useEffect(() => {\n    if (!isDownloading) {\n      return;\n    }\n    if (!hasReachedEnd) {\n      return;\n    }\n    const timeout = window.setTimeout(() => {\n      setStatus('done');\n    }, SETTLE_MS);\n    return () => window.clearTimeout(timeout);\n  }, [isDownloading, hasReachedEnd]);\n\n  useEffect(() => {\n    if (!isDone) {\n      return;\n    }\n    const timeout = window.setTimeout(() => {\n      setProgress(0);\n      setStatus('idle');\n    }, DONE_RESET_MS);\n    return () => window.clearTimeout(timeout);\n  }, [isDone]);\n\n  function startDownload() {\n    setProgress(0);\n    setStatus('downloading');\n  }\n\n  return (\n    <>\n      <Button\n        variant='outline'\n        onClick={startDownload}\n        disabled={isBusy}\n        focusableWhenDisabled\n        aria-busy={isDownloading}\n        className='isolate overflow-hidden data-disabled:opacity-100'\n      >\n        <span\n          aria-hidden='true'\n          className={FILL}\n          style={fillStyleOf(status, progress)}\n        />\n        <span className='relative flex'>\n          <AnimatePresence mode='popLayout' initial={false}>\n            <motion.span\n              key={iconKey}\n              className='flex'\n              variants={iconVariants}\n              initial='enter'\n              animate='visible'\n              exit='exit'\n              transition={SWAP_TRANSITION}\n            >\n              {iconOf(status)}\n            </motion.span>\n          </AnimatePresence>\n        </span>\n        <AnimatedSize axis='width' as='span' contentClassName='relative flex'>\n          <AnimatePresence mode='popLayout' initial={false}>\n            <motion.span\n              key={status}\n              className='flex'\n              variants={labelVariants}\n              initial='enter'\n              animate='visible'\n              exit='exit'\n              transition={SWAP_TRANSITION}\n            >\n              <DownloadLabel status={status} progress={progress} />\n            </motion.span>\n          </AnimatePresence>\n        </AnimatedSize>\n      </Button>\n      <span className='sr-only' role='status'>\n        {announcementOf(status)}\n      </span>\n    </>\n  );\n}\n",
      "type": "registry:component"
    }
  ],
  "categories": [
    "components",
    "button"
  ],
  "type": "registry:component"
}