# Copy Progress

> The Finder “Copy” progress window with items remaining, current file, progress bar, Stop button and an indeterminate “Preparing to copy…” state.

A shadcn/ui block from the [@nostalgia-ui](https://nostalgia-ui.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/nostalgia-ui/copy-progress).

- Page, with a live preview: https://shadcn.exchange/r/nostalgia-ui/copy-progress
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/nostalgia-ui/copy-progress-light-a901c6fde7c1efa1.png
- Registry item: https://nostalgia-ui.com/r/copy-progress.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://nostalgia-ui.com/r/copy-progress.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `https://nostalgia-ui.com/r/nostalgia-theme.json`, `https://nostalgia-ui.com/r/retro-button.json`, `https://nostalgia-ui.com/r/retro-icons.json`, `https://nostalgia-ui.com/r/retro-progress.json`, `https://nostalgia-ui.com/r/retro-window.json`

## Files

### `registry/new-york/blocks/copy-progress.tsx`

```tsx
"use client"

import * as React from "react"

import { cn } from "@/lib/utils"
import { RetroButton } from "@/registry/new-york/ui/retro-button"
import {
  RetroIconDocument,
  RetroIconFolder,
} from "@/registry/new-york/ui/retro-icons"
import { RetroProgress } from "@/registry/new-york/ui/retro-progress"
import { RetroWindow } from "@/registry/new-york/ui/retro-window"

/* ------------------------------------------------------------------ */
/*  Flying-paper animation (folder → folder)                           */
/* ------------------------------------------------------------------ */

function CopyAnimation({ active }: { active: boolean }) {
  return (
    <div className="relative h-8 w-[88px] shrink-0" aria-hidden>
      <RetroIconFolder className="absolute left-0 top-0" />
      <RetroIconFolder className="absolute right-0 top-0" />
      {active && (
        <span className="copy-progress-paper absolute left-[20px] top-[4px]">
          <RetroIconDocument size="sm" />
        </span>
      )}
      <style href="copy-progress-block" precedence="default">{`
        @keyframes copy-progress-fly {
          0%   { transform: translate(0, 6px); opacity: 0; }
          15%  { opacity: 1; }
          50%  { transform: translate(24px, -6px); }
          85%  { opacity: 1; }
          100% { transform: translate(48px, 6px); opacity: 0; }
        }
        .copy-progress-paper {
          animation: copy-progress-fly 1.2s linear infinite;
        }
        @media (prefers-reduced-motion: reduce) {
          .copy-progress-paper {
            animation: none;
            transform: translate(24px, -6px);
          }
        }
      `}</style>
    </div>
  )
}

/* ------------------------------------------------------------------ */
/*  CopyProgressBlock                                                  */
/* ------------------------------------------------------------------ */

interface CopyProgressBlockProps
  extends Omit<React.ComponentPropsWithoutRef<typeof RetroWindow>, "children" | "title"> {
  /** Number of items still to copy. */
  itemsRemaining?: number
  /** Name of the item currently being copied. */
  currentItem?: string
  /**
   * Progress 0–100. Leave undefined (or set `preparing`) for the
   * indeterminate "Preparing to copy…" state.
   */
  progress?: number
  /** Force the indeterminate "Preparing to copy…" state. */
  preparing?: boolean
  /** Called by the Stop button, Escape, or Command-period. */
  onStop?: () => void
  /** Window title. Defaults to "Copy". */
  title?: string
  /** Verb used in the labels. Defaults to "copy" (e.g. "move", "delete"). */
  verb?: string
}

const CopyProgressBlock = React.forwardRef<
  HTMLDivElement,
  CopyProgressBlockProps
>(function CopyProgressBlock(
  {
    itemsRemaining,
    currentItem,
    progress,
    preparing,
    onStop,
    title = "Copy",
    verb = "copy",
    className,
    onKeyDown,
    ...props
  },
  ref
) {
  const isPreparing = preparing ?? progress === undefined
  const Verb = verb.charAt(0).toUpperCase() + verb.slice(1)
  const gerund = `${Verb.replace(/e$/, "")}ing`
  const statusId = React.useId()

  return (
    <RetroWindow
      ref={ref}
      title={title}
      role="dialog"
      aria-label={title}
      aria-describedby={statusId}
      className={cn("w-full max-w-[420px]", className)}
      onKeyDown={(e) => {
        onKeyDown?.(e)
        if (e.defaultPrevented) return
        // Escape and Command-period are the classic Mac "cancel" keys
        if (e.key === "Escape" || (e.key === "." && (e.metaKey || e.ctrlKey))) {
          e.preventDefault()
          onStop?.()
        }
      }}
      {...props}
    >
      <div className="flex items-start gap-3 p-1">
        <CopyAnimation active={!isPreparing} />

        <div className="flex min-w-0 flex-1 flex-col gap-2">
          <div
            id={statusId}
            aria-live="polite"
            className="flex flex-col gap-1 font-[family-name:var(--os9-font-sans)] text-[10px] leading-[1.4] text-os9-black"
          >
            {isPreparing ? (
              <p>Preparing to {verb}…</p>
            ) : (
              <>
                {itemsRemaining !== undefined && (
                  <p>
                    Items remaining to be {verb === "copy" ? "copied" : `${verb.replace(/e$/, "")}ed`}:{" "}
                    <span className="tabular-nums">
                      {itemsRemaining.toLocaleString("en-US")}
                    </span>
                  </p>
                )}
                {currentItem && (
                  <p className="flex min-w-0 gap-1">
                    <span className="shrink-0">{gerund}:</span>
                    <span className="truncate" title={currentItem}>
                      {currentItem}
                    </span>
                  </p>
                )}
              </>
            )}
          </div>

          <RetroProgress
            value={isPreparing ? 0 : progress}
            indeterminate={isPreparing}
            aria-label={isPreparing ? `Preparing to ${verb}` : `${Verb} progress`}
          />

          <div className="flex justify-end pt-1">
            <RetroButton type="button" onClick={onStop} className="min-w-[70px]">
              Stop
            </RetroButton>
          </div>
        </div>
      </div>
    </RetroWindow>
  )
})
CopyProgressBlock.displayName = "CopyProgressBlock"

export { CopyProgressBlock }
export type { CopyProgressBlockProps }
```
