Folder Browser

The FolderBrowser component provides a tree view for navigating and managing folder hierarchies with support for creating, renaming, and deleting folders.

Basic Usage

import { FolderBrowser } from '@dropper/react'

function App() {
  return (
    <FolderBrowser
      onFolderChange={(folderId) => {
        console.log('Current folder:', folderId)
      }}
    />
  )
}

Props

interface FolderBrowserProps {
  initialPath?: string
  showFiles?: boolean
  allowCreate?: boolean
  allowDelete?: boolean
  allowRename?: boolean
  className?: string
  onFolderChange?: (folderId: string) => void
  onFileClick?: (file: FileResponseDto) => void
}

initialPath

Set the initial folder path:

{/* Start at root (default) */}
<FolderBrowser initialPath="/" />

{/* Start at specific folder */}
<FolderBrowser initialPath="/Documents/2024" />

showFiles

Display files in the current folder:

{/* Show files (default) */}
<FolderBrowser showFiles />

{/* Folders only */}
<FolderBrowser showFiles={false} />

allowCreate

Enable folder creation:

{/* Allow creation (default) */}
<FolderBrowser allowCreate />

{/* Read-only */}
<FolderBrowser allowCreate={false} />

allowDelete

Enable folder deletion:

{/* Allow deletion (default) */}
<FolderBrowser allowDelete />

{/* Prevent deletion */}
<FolderBrowser allowDelete={false} />

allowRename

Enable folder renaming:

{/* Allow renaming (default) */}
<FolderBrowser allowRename />

{/* Prevent renaming */}
<FolderBrowser allowRename={false} />

Event Handlers

onFolderChange

Called when the current folder changes:

<FolderBrowser
  onFolderChange={(folderId) => {
    console.log('Navigated to folder:', folderId)
    loadFolderContents(folderId)
  }}
/>

onFileClick

Called when a file is clicked:

<FolderBrowser
  showFiles
  onFileClick={(file) => {
    console.log('Clicked file:', file.originalFilename)
    openFile(file)
  }}
/>

Complete Examples

Basic Folder Navigation

import { FolderBrowser } from '@dropper/react'
import { useState } from 'react'

function FolderNavigation() {
  const [currentFolder, setCurrentFolder] = useState(null)
  
  return (
    <div>
      <h2>Browse Folders</h2>
      
      <FolderBrowser
        onFolderChange={(folderId) => {
          setCurrentFolder(folderId)
          console.log('Current folder:', folderId)
        }}
      />
      
      {currentFolder && (
        <p>Current folder ID: {currentFolder}</p>
      )}
    </div>
  )
}

File Explorer

import { FolderBrowser, FileList } from '@dropper/react'
import { useState } from 'react'

function FileExplorer() {
  const [currentFolder, setCurrentFolder] = useState(null)
  
  return (
    <div className="flex gap-4">
      {/* Left sidebar: Folder tree */}
      <div className="w-64 border-r">
        <FolderBrowser
          showFiles={false}
          onFolderChange={setCurrentFolder}
        />
      </div>
      
      {/* Right panel: File list */}
      <div className="flex-1">
        <FileList
          folderId={currentFolder}
          view="list"
        />
      </div>
    </div>
  )
}

Document Manager

import { FolderBrowser } from '@dropper/react'
import { useState } from 'react'

function DocumentManager() {
  const [selectedFile, setSelectedFile] = useState(null)
  
  return (
    <div className="document-manager">
      <div className="sidebar">
        <h3>Folders</h3>
        <FolderBrowser
          initialPath="/Documents"
          showFiles
          allowCreate
          allowRename
          allowDelete
          onFileClick={(file) => {
            setSelectedFile(file)
          }}
        />
      </div>
      
      <div className="content">
        {selectedFile ? (
          <div>
            <h2>{selectedFile.originalFilename}</h2>
            <iframe src={selectedFile.url} />
          </div>
        ) : (
          <p>Select a file to view</p>
        )}
      </div>
    </div>
  )
}

Read-Only Browser

import { FolderBrowser } from '@dropper/react'

function ReadOnlyBrowser() {
  return (
    <FolderBrowser
      allowCreate={false}
      allowDelete={false}
      allowRename={false}
      onFolderChange={(folderId) => {
        console.log('Viewing folder:', folderId)
      }}
    />
  )
}

Media Library Browser

import { FolderBrowser, FileList } from '@dropper/react'
import { useState } from 'react'

function MediaLibraryBrowser() {
  const [currentFolder, setCurrentFolder] = useState(null)
  const [selectedFiles, setSelectedFiles] = useState([])
  
  return (
    <div className="media-library">
      <div className="flex h-screen">
        {/* Folder tree */}
        <div className="w-64 border-r overflow-auto">
          <div className="p-4">
            <h3 className="font-semibold mb-4">Folders</h3>
            <FolderBrowser
              showFiles={false}
              allowCreate
              onFolderChange={setCurrentFolder}
            />
          </div>
        </div>
        
        {/* File grid */}
        <div className="flex-1 overflow-auto">
          <div className="p-4">
            <h3 className="font-semibold mb-4">
              {currentFolder ? 'Folder Contents' : 'All Files'}
            </h3>
            <FileList
              folderId={currentFolder}
              view="grid"
              selectable
              onFileSelect={setSelectedFiles}
            />
          </div>
        </div>
      </div>
      
      {/* Selection toolbar */}
      {selectedFiles.length > 0 && (
        <div className="fixed bottom-0 left-0 right-0 bg-white border-t p-4">
          <div className="flex items-center justify-between">
            <span>{selectedFiles.length} files selected</span>
            <div className="flex gap-2">
              <button>Download</button>
              <button>Move</button>
              <button>Delete</button>
            </div>
          </div>
        </div>
      )}
    </div>
  )
}

Folder Picker

import { FolderBrowser } from '@dropper/react'
import { useState } from 'react'

function FolderPicker({ onSelect }) {
  const [selectedFolder, setSelectedFolder] = useState(null)
  
  return (
    <div className="folder-picker">
      <h3>Select a folder</h3>
      
      <FolderBrowser
        showFiles={false}
        allowCreate
        onFolderChange={(folderId) => {
          setSelectedFolder(folderId)
        }}
      />
      
      <div className="actions">
        <button
          onClick={() => onSelect(selectedFolder)}
          disabled={!selectedFolder}
        >
          Select Folder
        </button>
        <button onClick={() => onSelect(null)}>
          Cancel
        </button>
      </div>
    </div>
  )
}

Features

Tree View

  • Hierarchical folder structure
  • Expand/collapse folders
  • Visual indentation
  • Folder icons

Folder Actions

  • Create: Add new folders
  • Rename: Rename existing folders
  • Delete: Remove folders (with confirmation)
  • Navigate: Click to navigate

File Display

  • Show files in current folder
  • File icons and names
  • Click to open files
  • File count indicators

Navigation

  • Breadcrumb navigation
  • Home button (root)
  • Parent folder navigation
  • Deep linking support

Styling

Custom Class Name

<FolderBrowser
  className="my-custom-browser"
/>

Themed Component

Uses theme from DropperProvider:

<DropperProvider
  publishableKey="pk_dropper_test_xxx"
  theme={{ primary: '#10b981' }}
>
  <FolderBrowser /> {/* Uses green theme */}
</DropperProvider>

Architecture

The component uses React Query for data management:

Folder Fetching

const { data: folders } = useQuery({
  queryKey: ['folders'],
  queryFn: () => client.listFolders(),
})

File Fetching

const { data: files } = useQuery({
  queryKey: ['files', currentFolderId],
  queryFn: () => client.listFiles({ folderId: currentFolderId }),
})

Mutations

const createMutation = useMutation({
  mutationFn: (name) => client.createFolder({ name }),
  onSuccess: () => queryClient.invalidateQueries(['folders']),
})

Accessibility

The component is fully accessible:

  • Keyboard navigation (Arrow keys, Enter, Escape)
  • Screen reader support
  • ARIA tree role
  • Focus management
  • Semantic HTML

Best Practices

1. Provide Event Handlers

<FolderBrowser
  onFolderChange={(folderId) => {
    // Update app state
  }}
  onFileClick={(file) => {
    // Handle file click
  }}
/>

2. Combine with FileList

const [folder, setFolder] = useState(null)

<div className="flex">
  <FolderBrowser onFolderChange={setFolder} />
  <FileList folderId={folder} />
</div>

3. Set Appropriate Permissions

{/* Admin view */}
<FolderBrowser
  allowCreate
  allowRename
  allowDelete
/>

{/* User view */}
<FolderBrowser
  allowCreate={false}
  allowRename={false}
  allowDelete={false}
/>

4. Handle Empty States

<FolderBrowser
  onFolderChange={(folderId) => {
    if (!folderId) {
      showMessage('No folder selected')
    }
  }}
/>

Common Patterns

Split View

<div className="flex">
  <FolderBrowser
    className="w-64"
    showFiles={false}
    onFolderChange={setCurrentFolder}
  />
  <FileList
    className="flex-1"
    folderId={currentFolder}
  />
</div>

Modal Picker

function FolderPickerModal({ open, onClose, onSelect }) {
  return (
    <Modal open={open} onClose={onClose}>
      <FolderBrowser
        showFiles={false}
        onFolderChange={(folderId) => {
          onSelect(folderId)
          onClose()
        }}
      />
    </Modal>
  )
}

Breadcrumb Navigation

The component includes built-in breadcrumb navigation showing the current path.

TypeScript Support

The component is fully typed:

import type { FolderBrowserProps, FileResponseDto } from '@dropper/react'

const props: FolderBrowserProps = {
  initialPath: '/',
  showFiles: true,
  onFolderChange: (folderId: string) => {
    console.log(folderId)
  },
  onFileClick: (file: FileResponseDto) => {
    console.log(file.url)
  },
}

<FolderBrowser {...props} />

Performance

The component is optimized for performance:

  • React Query Caching: Folders are cached
  • Lazy Loading: Folders load on expand
  • Optimistic Updates: UI updates immediately
  • Debounced Actions: Prevents excessive API calls

Next Steps