KQ-1645 · NAME/CLASH

Name Clash

An upload conflict resolver that decides one clash at a time: the file in the folder and the one being uploaded arrive side by side, sliding together until they touch on springs.glide, and picking an action previews it on the cards themselves — Replace slides the old card under the new one on glide, Keep both parts them on springs.snap while the upload's name grows its " (2)", Skip sets the upload aside. Apply sinks the pair on the exit ease and deals the next, Apply to all settles the rest in a cascade while the counter rolls down on snap, and an empty queue closes the panel to a one-line receipt. The actions are a radiogroup — arrows pick and the cards answer — Enter applies from anywhere in the panel and A applies to every remaining conflict.

KQ-1645 · NAME/CLASH

Name clash

Coldbrook / Brand / Exports

6left
  1. 1menu-board.png, now deciding
  2. 2price-list.xlsx
  3. 3roastery-story.docx
  4. 4q4-launch-deck.key
  5. 5cafe-ambience.wav
  6. 6label-front.png
In foldermenu-board.png2.1 MB · 3 d agoMara
Uploadingmenu-board.png3.5 MB · 4 min agoInes+1.4 MBNewer

The upload is saved as menu-board (2).png.

6 of 6 left · keep both · menu-board.png

Install

One command — the source lands in your repo. Or copy it from the Code tab.

$ pnpm dlx shadcn@latest add @kinetiq/name-clash

Props

conflicts
NameClashConflict[]
default defaultNameClashConflicts
The queue: { id, existing, incoming }, each file { name, size, modified, author, kind?, seed? }.
action / defaultAction
"replace" | "keep" | "skip"
default — / "keep"
The action for the current conflict, controlled or initial.
onActionChange
(action) => void
default —
Fires from the click or arrow key that picked an action.
onResolve
(conflict, action, name) => void
default —
Fires once per file as it is settled, with the name the upload ends up with (empty when skipped).
onDone
(results: NameClashResult[]) => void
default —
Fires when the queue empties, with every decision in order.
onCancel
(remaining: NameClashConflict[]) => void
default —
The close button was pressed; these conflicts are left unsettled.
status
"ready" | "loading" | "error"
default "ready"
Loading blanks the thumbnails and reads Comparing files; error explains the folder's file couldn't be read and makes Replace unavailable.
now
number
default defaultNameClashNow
The moment ages count from, in ms; never read from the clock during render.
folder
string
default "Coldbrook / Brand / Exports"
The destination folder, shown under the title.
nextName
(conflict) => string
default inserts " (2)" before the extension
The name Keep both gives the upload; the part it adds grows in on the card.
cardLayout
"pair" | "stacked" | "tilted"
default "pair"
How the two cards meet: side by side, one above the other, or turned toward each other and overlapping the seam.
thumbSize
number
default 80
Thumbnail edge in px, capped at 48 below a 32rem container.
accent
string
default "var(--accent-bright)"
The chosen action, the upload's edge, the queue's current chip and Apply, any CSS colour.
title
string
default "Name clash"
The panel's heading and accessible name.
height
number
default 540
Height of the open panel in px; it glides down to a 64px receipt when the queue empties.
disabled
boolean
default false
Nothing can be picked or applied.
className
string
default —
Classes for the panel.