{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "card-progress",
  "type": "registry:item",
  "title": "Progress Card",
  "description": "A file upload progress card with multiple file tracking.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "https://roiui.com/r/button.json",
    "https://roiui.com/r/card.json",
    "https://roiui.com/r/progress.json"
  ],
  "files": [
    {
      "path": "registry/brook/blocks/card-progress/components/progress-card.tsx",
      "content": "\"use client\";\n\nimport { AlertCircle, Check } from \"lucide-react\";\nimport { Card, CardContent } from \"@/registry/brook/ui/card/card\";\nimport styles from \"./progress-card.module.css\";\n\nexport type StepStatus = \"pending\" | \"in_progress\" | \"complete\" | \"error\" | \"warning\";\n\nexport type Step = {\n  id: string;\n  title: string;\n  description?: string;\n  status: StepStatus;\n};\n\ntype StepIndicatorProps = {\n  status: StepStatus;\n  isFinal?: boolean;\n};\n\nfunction AnimatedClock({ className }: { className?: string }) {\n  return (\n    <svg\n      className={className}\n      fill=\"none\"\n      height=\"12\"\n      stroke=\"currentColor\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      strokeWidth=\"2.5\"\n      viewBox=\"0 0 24 24\"\n      width=\"12\"\n    >\n      <circle cx=\"12\" cy=\"12\" r=\"10\" />\n      <line x1=\"12\" x2=\"12\" y1=\"12\" y2=\"7\" />\n      <line className={styles.clockHand} x1=\"12\" x2=\"15\" y1=\"12\" y2=\"13\" />\n    </svg>\n  );\n}\n\nfunction getIndicatorConfig(status: StepStatus, isFinal: boolean) {\n  switch (status) {\n    case \"complete\":\n      return {\n        className: `${styles.spinnerToCheck} ${isFinal ? styles.finalCheck : \"\"}`,\n        icon: \"check\",\n      } as const;\n    case \"in_progress\":\n      return { className: styles.spinnerCircle, icon: null } as const;\n    case \"error\":\n      return { className: styles.errorCircle, icon: \"error\" } as const;\n    case \"warning\":\n      return { className: styles.clockCircle, icon: \"clock\" } as const;\n    default:\n      return { className: styles.pendingCircle, icon: null } as const;\n  }\n}\n\nfunction StepIndicator({ status, isFinal = false }: StepIndicatorProps) {\n  const { className, icon } = getIndicatorConfig(status, isFinal);\n  const showSpinner = status === \"in_progress\" || status === \"complete\";\n  const isComplete = status === \"complete\";\n\n  return (\n    <div className={styles.indicatorWrapper}>\n      {showSpinner && <div className={`${styles.spinnerCircleOverlay} ${isComplete ? styles.spinnerFading : \"\"}`} />}\n      <div className={className}>\n        {icon === \"clock\" && <AnimatedClock className={styles.clockIcon} />}\n        {icon === \"check\" && <Check className={styles.checkIcon} size={14} strokeWidth={3} />}\n        {icon === \"error\" && <AlertCircle className={styles.errorIcon} size={14} strokeWidth={2.5} />}\n      </div>\n    </div>\n  );\n}\n\ntype ProgressCardProps = {\n  steps: Step[];\n};\n\nexport function ProgressCard({ steps = [] }: ProgressCardProps) {\n  const allComplete = steps.length > 0 && steps.every((s) => s.status === \"complete\");\n\n  return (\n    <Card className={styles.card}>\n      <CardContent className={styles.content}>\n        <div className={styles.stepList}>\n          {steps.map((step, index) => {\n            const isHighlighted = step.status === \"in_progress\" || step.status === \"error\" || step.status === \"warning\";\n            const showDescription = isHighlighted && step.description;\n            const isFinal = index === steps.length - 1 && allComplete;\n\n            return (\n              <div className={`${styles.stepItem} ${styles[step.status]}`} key={step.id}>\n                <div className={styles.stepIndicator}>\n                  <StepIndicator isFinal={isFinal} status={step.status} />\n                </div>\n                <div className={styles.stepContent}>\n                  <span\n                    className={`${styles.stepTitle} ${step.status === \"in_progress\" ? styles.stepTitleShimmer : \"\"}`}\n                  >\n                    {step.title}\n                  </span>\n                  {step.description ? (\n                    <div className={`${styles.descriptionWrapper} ${showDescription ? styles.descriptionVisible : \"\"}`}>\n                      <span className={styles.stepDescription}>{step.description}</span>\n                    </div>\n                  ) : null}\n                </div>\n              </div>\n            );\n          })}\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n",
      "type": "registry:file",
      "target": "~/components/blocks/card-progress/progress-card.tsx"
    },
    {
      "path": "registry/brook/blocks/card-progress/components/progress-card.module.css",
      "content": ".card {\n  width: 100%;\n  max-width: 320px;\n}\n\n.card[data-slot=\"card\"] {\n  padding: 16px;\n  background-color: oklch(from var(--card) l c h / 0.6);\n  box-shadow:\n    0 0 0 1px oklch(from var(--border) l c h / 0.5),\n    0 1px 3px 0 #0000000d,\n    0 1px 2px -1px #0000000d;\n}\n\n.content {\n  display: flex;\n  flex-direction: column;\n}\n\n.stepList {\n  display: flex;\n  flex-direction: column;\n  gap: 4px;\n}\n\n.stepItem {\n  display: flex;\n  align-items: flex-start;\n  gap: 12px;\n  padding: 12px;\n  border-radius: var(--radius);\n  transition: background-color 200ms ease-out;\n  position: relative;\n}\n\n.pending {\n}\n.in_progress {\n}\n.complete {\n}\n.error {\n}\n.warning {\n}\n\n.stepIndicator {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 24px;\n  height: 24px;\n  flex-shrink: 0;\n  position: relative;\n}\n\n.stepItem:not(:last-child)::after {\n  content: \"\";\n  position: absolute;\n  left: 23px;\n  top: 36px;\n  width: 1px;\n  height: calc(100% - 16px);\n  background-color: var(--border);\n  transition: background-color 200ms ease;\n  pointer-events: none;\n  z-index: 0;\n}\n\n.stepItem.complete:not(:last-child)::after {\n  background-color: var(--foreground);\n}\n\n.indicatorWrapper {\n  position: relative;\n  width: 20px;\n  height: 20px;\n}\n\n.spinnerCircleOverlay {\n  position: absolute;\n  inset: 0;\n  width: 20px;\n  height: 20px;\n  border-radius: 50%;\n  border: 2px solid var(--border);\n  border-top-color: var(--foreground);\n  background-color: var(--card);\n  animation: spin 0.8s linear infinite;\n  opacity: 1;\n  transition: opacity 300ms ease-out;\n  will-change: transform;\n  z-index: 2;\n}\n\n.spinnerFading {\n  opacity: 0;\n}\n\n.pendingCircle {\n  width: 20px;\n  height: 20px;\n  border-radius: 50%;\n  border: 2px solid var(--border);\n  background-color: var(--card);\n  position: relative;\n  z-index: 1;\n}\n\n.spinnerCircle {\n  width: 20px;\n  height: 20px;\n  border-radius: 50%;\n  border: 2px solid var(--border);\n  border-top-color: var(--foreground);\n  background-color: var(--card);\n  animation:\n    spin 0.8s linear infinite,\n    fadeIn 150ms ease-out;\n  will-change: transform, opacity;\n  position: relative;\n  z-index: 1;\n}\n\n@keyframes fadeIn {\n  from {\n    opacity: 0;\n  }\n  to {\n    opacity: 1;\n  }\n}\n\n.spinnerToCheck {\n  width: 20px;\n  height: 20px;\n  border-radius: 50%;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  position: relative;\n  z-index: 1;\n  background-color: var(--foreground);\n  animation: circleSettle 200ms cubic-bezier(0.165, 0.84, 0.44, 1) forwards;\n}\n\n@keyframes circleSettle {\n  0% {\n    opacity: 0;\n    transform: scale(0.97);\n  }\n  100% {\n    opacity: 1;\n    transform: scale(1);\n  }\n}\n\n@keyframes spin {\n  from {\n    transform: rotate(0deg);\n  }\n  to {\n    transform: rotate(360deg);\n  }\n}\n\n.checkIcon {\n  color: var(--background);\n  position: relative;\n  z-index: 1;\n  opacity: 0;\n  animation: iconAppear 150ms cubic-bezier(0.165, 0.84, 0.44, 1) 50ms forwards;\n}\n\n@keyframes iconAppear {\n  from {\n    opacity: 0;\n    transform: scale(0.95);\n  }\n  to {\n    opacity: 1;\n    transform: scale(1);\n  }\n}\n\n.clockCircle {\n  width: 20px;\n  height: 20px;\n  border-radius: 50%;\n  background-color: var(--warning);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  position: relative;\n  z-index: 1;\n}\n\n.clockIcon {\n  color: var(--warning-foreground);\n}\n\n.clockHand {\n  transform-origin: 12px 12px;\n  animation: clockRotate 2s linear infinite;\n}\n\n@keyframes clockRotate {\n  from {\n    transform: rotate(0deg);\n  }\n  to {\n    transform: rotate(360deg);\n  }\n}\n\n.errorCircle {\n  width: 20px;\n  height: 20px;\n  border-radius: 50%;\n  background-color: var(--destructive);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  position: relative;\n  z-index: 1;\n}\n\n.errorIcon {\n  color: var(--destructive-foreground);\n}\n\n.stepItem.error .stepTitle {\n  color: var(--destructive);\n}\n\n.finalCheck {\n  background-color: var(--success, #22c55e);\n  animation: circleSettle 200ms cubic-bezier(0.165, 0.84, 0.44, 1) forwards;\n}\n\n.stepContent {\n  display: flex;\n  flex-direction: column;\n  gap: 2px;\n  padding-top: 2px;\n  min-height: 24px;\n}\n\n.stepTitle {\n  font-size: 0.9375rem;\n  font-weight: 500;\n  color: var(--muted-foreground);\n  transition: color 200ms ease;\n}\n\n.stepItem.complete .stepTitle {\n  color: oklch(from var(--foreground) l c h / 0.5);\n}\n\n.stepItem.error .stepTitle {\n  color: var(--destructive);\n}\n\n.stepItem.error .stepDescription {\n  color: var(--destructive-foreground);\n}\n\n.stepItem.warning .stepTitle {\n  color: var(--warning);\n}\n\n.stepTitleShimmer {\n  background: linear-gradient(\n    90deg,\n    var(--muted-foreground) 0%,\n    var(--muted-foreground) 40%,\n    color-mix(in oklch, var(--foreground) 50%, var(--muted-foreground)) 50%,\n    var(--muted-foreground) 60%,\n    var(--muted-foreground) 100%\n  );\n  background-size: 200% 100%;\n  background-clip: text;\n  -webkit-background-clip: text;\n  -webkit-text-fill-color: transparent;\n  animation: shimmer 1.5s ease-in-out infinite;\n}\n\n@keyframes shimmer {\n  0% {\n    background-position: 100% 0;\n  }\n  100% {\n    background-position: -100% 0;\n  }\n}\n\n.descriptionWrapper {\n  display: grid;\n  grid-template-rows: 0fr;\n  opacity: 0;\n  transition:\n    grid-template-rows 250ms cubic-bezier(0.165, 0.84, 0.44, 1),\n    opacity 200ms ease-out;\n}\n\n.descriptionWrapper.descriptionVisible {\n  grid-template-rows: 1fr;\n  opacity: 1;\n}\n\n.stepDescription {\n  font-size: 0.8125rem;\n  color: var(--muted-foreground);\n  overflow: hidden;\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .spinnerCircle {\n    animation: none;\n  }\n\n  .spinnerCircleOverlay {\n    animation: none;\n    transition: none;\n  }\n\n  .spinnerFading {\n    opacity: 0;\n  }\n\n  .clockHand {\n    animation: none;\n  }\n\n  .checkIcon {\n    animation: none;\n    opacity: 1;\n  }\n\n  .spinnerToCheck {\n    animation: none;\n  }\n\n  .finalCheck {\n    animation: none;\n  }\n\n  .descriptionWrapper {\n    transition: none;\n  }\n\n  .stepItem {\n    transition: none;\n  }\n\n  .stepTitle {\n    transition: none;\n  }\n\n  .stepTitleShimmer {\n    animation: none;\n    background: none;\n    -webkit-text-fill-color: var(--muted-foreground);\n  }\n}\n",
      "type": "registry:file",
      "target": "~/components/blocks/card-progress/progress-card.module.css"
    }
  ]
}