Reference / React

React components

Optional subpath of @sentroy-co/client-sdk/react. React and react-dom are declared as optional peer dependencies— server-only consumers don't need to install them.

npm install react react-dom

MediaManager#

Drop-in storage browser and uploader. Talks to the same Sentroy client you already configured. Renders Tailwind classes that consume your design tokens — no CSS to import.

"use client"

import { Sentroy } from "@sentroy-co/client-sdk"
import { MediaManager } from "@sentroy-co/client-sdk/react"

const client = new Sentroy({
  baseUrl: "https://sentroy.com",
  companySlug: "my-company",
  accessToken: "stk_...",
})

export default function Page() {
  return (
    <MediaManager
      client={client}
      multiple
      accept="image/*"
      onChange={(selected) => console.log(selected)}
      onSelect={(selected) => console.log("confirmed:", selected)}
    />
  )
}

Features

  • Bucket selector (auto-picks first if bucketSlug not provided)
  • Search (filename) + file-type filter (image / video / audio / pdf / doc / archive / code)
  • Upload via button and drag-and-drop
  • Single or multi selection (multiple prop)
  • initialValue accepts Media[] or string[] (id list) — pre-selected on mount, fires onChange immediately so parent state stays in sync
  • Press Space while a card is selected → opens it in fullscreen Lightbox. Esc closes, ←/→ step through siblings
  • Detail pane on the right (large screens) — preview, metadata, delete, "Use selection" CTA when onSelect provided

Props

NameTypeDescription
clientrequiredSentroyConfigured client instance
bucketSlugstringInitial bucket; default = first one in the list
multiplebooleanAllow multi-selection. Default false
maxItemsnumberCap for multi-mode. New selections are silently blocked once reached. Ignored when multiple=false
acceptstringFile type filter — same syntax as <input accept>: "image/*", ".pdf,.docx", combos
initialValueArray<Media | string>Pre-selected items (objects or ids)
onChange(selected: Media[]) => voidFires on every selection change
onSelect(selected: Media[]) => voidFires on confirm — picker dialogs use this
bucketFilter(b: Bucket) => booleanFilter the bucket dropdown — hide system buckets
showDetailsPanebooleanDefault true
showBucketSelectorbooleanDefault true
classNamestringRoot wrapper class
classNamesMediaManagerClassNamesPer-region class overrides (see theming)

Theming

The component uses Tailwind utility classes that consume your design tokens (bg-background, text-foreground, border-border, text-muted-foreground, bg-muted, etc.). Drop-in usage in any shadcn-style codebase needs no extra setup.

For finer control, override individual sections via classNames:

<MediaManager
  client={client}
  className="h-[600px] rounded-2xl border-purple-200"
  classNames={{
    toolbar: "bg-purple-50",
    uploadButton: "bg-purple-600 text-white",
    cardSelected: "ring-purple-400 border-purple-400",
    grid: "sm:grid-cols-2 md:grid-cols-3",
  }}
/>

Available keys: root, toolbar, searchInput, filterSelect, uploadButton, bucketSelect, grid, card, cardSelected, thumbnail, cardMeta, empty, details, dropZoneOverlay.

MediaManagerTrigger#

A wrapper that turns any clickable element into a media picker. When the user clicks the trigger, a portal-rendered modal opens with MediaManager inside, and onSelect fires with the confirmed selection.

The use case: you don't want a giant manager taking up real estate on your settings page — you just want a "Change avatar" button (or even a clickable avatar thumbnail) that pops the picker on demand.

"use client"

import { Sentroy } from "@sentroy-co/client-sdk"
import { MediaManagerTrigger } from "@sentroy-co/client-sdk/react"

const client = new Sentroy({
  baseUrl: "https://sentroy.com",
  companySlug: "my-company",
  accessToken: "stk_...",
})

export function AvatarPicker({
  current,
  onChange,
}: {
  current: string | null
  onChange: (url: string) => void
}) {
  return (
    <MediaManagerTrigger
      client={client}
      maxItems={1}
      accept="image/*"
      title="Choose your avatar"
      description="Pick an existing image or upload a new one."
      trigger={
        <button className="rounded-full ring-2 ring-border hover:ring-primary">
          {current ? (
            <img src={current} alt="" className="size-10 rounded-full" />
          ) : (
            <span className="grid size-10 place-items-center rounded-full bg-muted text-xs">
              ?
            </span>
          )}
        </button>
      }
      onSelect={(media) => {
        if (media[0]?.url) onChange(media[0].url)
      }}
    />
  )
}

Multi-select with cap

<MediaManagerTrigger
  client={client}
  maxItems={5}
  accept="image/*,video/*"
  trigger={<Button>Add gallery items</Button>}
  onSelect={(media) => setGallery(media)}
/>

maxItems > 1automatically enables multi-mode. Once the user reaches the cap, additional clicks on unselected cards are silently no-op'd — they have to deselect something to swap.

Controlled mode

If you want the parent to drive open/close (e.g. opening from a context menu), pass open + onOpenChange. The trigger is still rendered so its click also opens the modal — to render only the modal, pass an empty fragment for trigger.

const [open, setOpen] = useState(false)

<MediaManagerTrigger
  client={client}
  open={open}
  onOpenChange={setOpen}
  trigger={<></>}
  onSelect={(media) => { /* … */ }}
/>

Props

NameTypeDescription
clientrequiredSentroySame client you pass to MediaManager
triggerrequiredReactNodeThe clickable element. Wrapped in <span role="button"> with click + keyboard (Enter / Space) handlers
onSelectrequired(selected: Media[]) => voidFires when user confirms; modal auto-closes
maxItemsnumber1 = single (default), >1 = multi up to cap
acceptstringSame <input accept> syntax — applies to upload and grid filter
titlestringModal heading. Default "Select media"
descriptionstringSubheading under the title
openbooleanControlled open state
onOpenChange(open: boolean) => voidControlled change handler
disabledbooleanTrigger ignores clicks; visual disabled state
confirmLabelstringDefault "Use selection"
cancelLabelstringDefault "Cancel"
modalClassNamestringClass on the modal panel
triggerClassNamestringClass on the trigger wrapper span
...restMediaManagerPropsbucketSlug, bucketFilter, showDetailsPane, classNames, etc. forwarded to the inner MediaManager

Helpers#

Tiny utilities re-exported from the React subpath so you don't have to depend on the core SDK package separately.

import {
  cn,           // tiny class joiner
  formatBytes,  // 1234 → "1.21 KB"
  detectKind,   // image | video | audio | pdf | doc | archive | code | other
  matchAccept,  // matchAccept(file, "image/*,.pdf") → boolean
  KIND_LABELS,
  type MediaKind,
} from "@sentroy-co/client-sdk/react"

Requirements

  • Node.js 18+ (uses native fetch)
  • React 18+ (only if you import from /react)
  • Tailwind CSS in the host app (only for React components)