Move Readback

Before moving an item, reads back who gains or loses access, which links break and which settings change, so a private doc doesn't land in a public folder.

StopsHelps prevent a private doc being moved into a public folder

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/move-readback.json

The files land in your project. They're yours to edit.

Usage

move-readback-usage.tsx
import { MoveReadback, type MoveLocation } from "@/components/ui/move-readback"

<MoveReadback
  items={[{ name: "Q3 pricing.pdf", pathLinks: 4, directShares: 2 }]}
  from={FROM}
  to={DESTINATIONS[dest]}
  onCancel={() => setDest("public")}
  onMove={({ keepRedirect }) =>
    new Promise<void>((resolve) =>
      setTimeout(() => {
        setDone(`Moved to ${DESTINATIONS[dest].path.join(" / ")}${keepRedirect ? ", with a redirect at the old path" : ""}.`)
        resolve()
      }, 600),
    )
  }
/>

What it catches

  • Moves that make an item visible to more people
  • Moves that remove access for people who had it
  • Path links that will break, with a keep-redirect option
  • Inherited settings (retention, sharing) that change at the destination

Props

MoveReadback props
PropTypeDefault
items* (required)

Items being moved.

MoveItem[]—
from* (required)

Where they are now.

MoveLocation—
to* (required)

Where they are going.

MoveLocation—
onMove* (required)

Runs on confirm. `keepRedirect` is true when old links should keep working. Return a promise to show a pending state.

(options: { keepRedirect: boolean }) => void | Promise<void>—
onCancel

Called when the user cancels.

() => void—
canKeepRedirect

Offer to leave a redirect/shortcut at the old path. Omit when the product can't.

booleantrue
locale

BCP 47 locale for counts. Defaults to the viewer's.

string—
messages

Every visible string (see `MOVE_READBACK_MESSAGES`). The single-string props below still work and win.

Partial<MoveReadbackMessages>—
gainedText(visibility: string, total: string) => string—
lostText(count: string, kept: string) => string—
sameTextstring—
brokenText(count: string) => string—
redirectLabelstring—
redirectedText(count: string) => string—
moveLabel(what: string) => string—
widenLabel(what: string, count: number) => string—
title(what: string) => React.ReactNode—
pendingLabelstring—
cancelLabelstring—

* required. Also accepts the native props of <form> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Form labelled by its title; paths are labelled ordered lists
  • Impact list is a polite live region; the arrow between states has an accessible name
  • Highlight animation is skipped under reduced motion

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

This is a UX guard rail. Enforce on the server: Re-check permissions and apply the move server-side; recompute access after it.

  • Viewer counts and settings come from your props; it doesn't query permissions
  • Direct shares on the item are only as accurate as your data

What it stops

Helps prevent a private doc being moved into a public folder

Also known as

move to · folder · permissions · sharing · google drive · notion · breadcrumbs · access

Field type

Dependencies

Pairs well with