KQ-1265 · UPLOAD/ORBIT
Upload Orbit
An upload button over a real file input whose files orbit it while they go up: chosen or dropped files become dots that pop into a waiting line on the top edge on the snap spring, step out on the glide spring, and then sit round the button's perimeter at their progress, walked by arc length so one lap is the whole file, chasing each reported value on a critically damped spring while a softer spring drags a fading comet tail behind them. A finished dot completes its lap and hops into the count in the label, where the badge recoils on the recoil spring and the digit rolls on snap; a failed dot leaves along its tangent, falls and fades red while a red counter appears on the badge. Click, Enter or Space opens the system picker and files can be dropped anywhere on it; Escape cancels what is in flight, and after a batch with failures a press queues them again.
Site photos
Fieldline · Inspection 14, Basin Road
no photos yet · drop or choose
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/upload-orbit
Props
- onUpload
- (file: File, progress: (share: number) => void, signal: AbortSignal) => Promise<unknown>
- default —
- Uploads one file: report progress from 0 to 1 as it goes and settle the promise when it is done or failed. Called for at most queue files at once; the signal aborts on Escape or unmount.
- files
- UploadOrbitFile[]
- default —
- Controlled list of files ({ id, name, size?, progress, status: "queued" | "uploading" | "done" | "error", error? }). The button reports every list it wants through onFilesChange and draws this one.
- onFilesChange
- (files: UploadOrbitFile[]) => void
- default —
- Every new list: files added, progress, completions, failures, retries.
- onSelect
- (files: File[]) => void
- default —
- The files the visitor chose or dropped that passed accept and maxSize.
- accept
- string
- default —
- What the picker offers and a drop accepts, as for a file input ("image/*,.pdf"). Refused files fall off at once and cannot be retried.
- multiple
- boolean
- default true
- Take several files at a time; off, only the first of a drop is taken.
- maxSize
- number
- default —
- The largest file accepted, in bytes. Larger ones fall off at once.
- tail
- number
- default 0.6
- How far each dot's comet tail trails it while it moves, 0 to 1, capped at 35% of the lap. 0 draws no tail.
- speed
- number
- default 0.5
- How quickly a dot runs to its reported progress, 0 to 1: a lazy glide that smooths a chunky connection, or a tight follow that dashes.
- queue
- number
- default 2
- How many files fly at once, 1 to 4; the rest wait in line as smaller dots.
- label
- string
- default "Upload"
- The text at rest.
- pendingLabel
- string
- default "Uploading"
- The text while files are queued, in flight or landing.
- successLabel
- string
- default "Uploaded"
- The text once a batch has landed.
- errorLabel
- string
- default "Retry"
- The text after a batch with failures that can be retried; a press queues them again.
- state
- "idle" | "pending" | "success" | "error"
- default —
- Controlled button state. Without it, the state follows the files: pending until the last finished dot has landed.
- onStateChange
- (state: "idle" | "pending" | "success" | "error") => void
- default —
- Each state the button moves to, from the change that caused it.
- successHold
- number
- default 1600
- How long Uploaded holds before the button rests, in ms.
- errorHold
- number
- default 4000
- How long Retry holds before the button rests, in ms.
- size
- "sm" | "md" | "lg"
- default "md"
- 32, 40 or 48 px tall; the dots, their spacing and the orbit scale with it.
- accent
- string
- default "var(--accent-bright)"
- The orbit's colour: dots, tails and the lit track. Any CSS colour.
- sound
- boolean
- default false
- Play a pop for the drop or pick and a rising plip as each dot takes its place, only in answer to the visitor. Landings and failures are silent.
- disabled
- boolean
- default false
- Dims the button and refuses picks and drops.
- className
- string
- default —
- Merged onto the root's classes (the button and the 12 px frame its orbit stays inside).
- ref
- React.Ref<UploadOrbitHandle>
- default —
- An imperative handle: add(files) queues files as if dropped, open() opens the picker, cancel() stops everything in flight, retry() queues the failed files again.
| Prop | Type | Default | Description |
|---|---|---|---|
| onUpload | (file: File, progress: (share: number) => void, signal: AbortSignal) => Promise<unknown> | — | Uploads one file: report progress from 0 to 1 as it goes and settle the promise when it is done or failed. Called for at most queue files at once; the signal aborts on Escape or unmount. |
| files | UploadOrbitFile[] | — | Controlled list of files ({ id, name, size?, progress, status: "queued" | "uploading" | "done" | "error", error? }). The button reports every list it wants through onFilesChange and draws this one. |
| onFilesChange | (files: UploadOrbitFile[]) => void | — | Every new list: files added, progress, completions, failures, retries. |
| onSelect | (files: File[]) => void | — | The files the visitor chose or dropped that passed accept and maxSize. |
| accept | string | — | What the picker offers and a drop accepts, as for a file input ("image/*,.pdf"). Refused files fall off at once and cannot be retried. |
| multiple | boolean | true | Take several files at a time; off, only the first of a drop is taken. |
| maxSize | number | — | The largest file accepted, in bytes. Larger ones fall off at once. |
| tail | number | 0.6 | How far each dot's comet tail trails it while it moves, 0 to 1, capped at 35% of the lap. 0 draws no tail. |
| speed | number | 0.5 | How quickly a dot runs to its reported progress, 0 to 1: a lazy glide that smooths a chunky connection, or a tight follow that dashes. |
| queue | number | 2 | How many files fly at once, 1 to 4; the rest wait in line as smaller dots. |
| label | string | "Upload" | The text at rest. |
| pendingLabel | string | "Uploading" | The text while files are queued, in flight or landing. |
| successLabel | string | "Uploaded" | The text once a batch has landed. |
| errorLabel | string | "Retry" | The text after a batch with failures that can be retried; a press queues them again. |
| state | "idle" | "pending" | "success" | "error" | — | Controlled button state. Without it, the state follows the files: pending until the last finished dot has landed. |
| onStateChange | (state: "idle" | "pending" | "success" | "error") => void | — | Each state the button moves to, from the change that caused it. |
| successHold | number | 1600 | How long Uploaded holds before the button rests, in ms. |
| errorHold | number | 4000 | How long Retry holds before the button rests, in ms. |
| size | "sm" | "md" | "lg" | "md" | 32, 40 or 48 px tall; the dots, their spacing and the orbit scale with it. |
| accent | string | "var(--accent-bright)" | The orbit's colour: dots, tails and the lit track. Any CSS colour. |
| sound | boolean | false | Play a pop for the drop or pick and a rising plip as each dot takes its place, only in answer to the visitor. Landings and failures are silent. |
| disabled | boolean | false | Dims the button and refuses picks and drops. |
| className | string | — | Merged onto the root's classes (the button and the 12 px frame its orbit stays inside). |
| ref | React.Ref<UploadOrbitHandle> | — | An imperative handle: add(files) queues files as if dropped, open() opens the picker, cancel() stops everything in flight, retry() queues the failed files again. |