Skip to content

Latest commit

 

History

History
416 lines (320 loc) · 7.72 KB

File metadata and controls

416 lines (320 loc) · 7.72 KB

@spacedrive/explorer

File management and explorer components for SpaceUI.

Installation

bun add @spacedrive/explorer @spacedrive/primitives
# or
npm install @spacedrive/explorer @spacedrive/primitives

Peer dependencies:

  • react ^18.0.0 || ^19.0.0
  • react-dom ^18.0.0 || ^19.0.0
  • @tanstack/react-virtual ^3.0.0

Usage

import { FileGrid, PathBar, Inspector, KindIcon, TagPill } from '@spacedrive/explorer';
import type { FileInfo, TagInfo } from '@spacedrive/explorer';

const file: FileInfo = {
  id: '1',
  name: 'document.pdf',
  path: '/docs/document.pdf',
  kind: 'document',
  size: 1024000,
  modifiedAt: new Date(),
  createdAt: new Date(),
  isDirectory: false,
  extension: 'pdf',
};

const tag: TagInfo = {
  id: '1',
  name: 'Important',
  color: '#ef4444',
};

function FileBrowser() {
  return (
    <div className="flex gap-4">
      <div className="flex-1">
        <PathBar 
          path={['Home', 'Documents', 'Projects']} 
          onNavigate={(index) => console.log('Navigate to', index)} 
        />
        <div className="mt-4">
          <KindIcon kind="document" />
          <TagPill tag={tag} onRemove={() => console.log('Remove tag')} />
        </div>
      </div>
      <Inspector file={file} tags={[tag]} />
    </div>
  );
}

Components

Display Components

KindIcon

File type icons:

import { KindIcon } from '@spacedrive/explorer';
import type { FileKind } from '@spacedrive/explorer';

<KindIcon kind="document" />      {/* Document icon */}
<KindIcon kind="image" />         {/* Image icon */}
<KindIcon kind="video" />         {/* Video icon */}
<KindIcon kind="audio" />         {/* Audio icon */}
<KindIcon kind="archive" />       {/* Archive icon */}
<KindIcon kind="code" />          {/* Code icon */}
<KindIcon kind="unknown" />       {/* Question mark */}

Sizes: sm | md | lg

<KindIcon kind="image" size="lg" />

TagPill

Colored tag pill:

import { TagPill } from '@spacedrive/explorer';
import type { TagInfo } from '@spacedrive/explorer';

const tag: TagInfo = {
  id: '1',
  name: 'Important',
  color: '#ef4444',  // Any valid CSS color
};

<TagPill tag={tag} />

// With remove button
<TagPill tag={tag} onRemove={() => console.log('Remove')} />

FileThumb

File thumbnail renderer:

import { FileThumb } from '@spacedrive/explorer';
import type { FileInfo } from '@spacedrive/explorer';

const file: FileInfo = {
  id: '1',
  name: 'photo.jpg',
  kind: 'image',
  thumbnailUrl: '/thumbs/photo.jpg',
  // ... other fields
};

<FileThumb file={file} size="sm" />  {/* 32px */}
<FileThumb file={file} size="md" />  {/* 48px */}
<FileThumb file={file} size="lg" />  {/* 64px */}

For images with thumbnailUrl, displays the image. Otherwise shows kind icon.

Navigation Components

PathBar

Breadcrumb navigation:

import { PathBar } from '@spacedrive/explorer';

<PathBar
  path={['Home', 'Documents', 'Projects', 'MyProject']}
  onNavigate={(index) => {
    // index -1 = home, 0 = Home, 1 = Documents, etc.
    console.log('Navigate to level', index);
  }}
  homeLabel="Home"  // Optional, defaults to "Home"
/>

Features:

  • Home button always shown
  • Long paths collapsed with dropdown
  • Click any segment to navigate

File Views

FileGrid

Grid layout of files:

import { FileGrid } from '@spacedrive/explorer';
import type { FileInfo } from '@spacedrive/explorer';

const files: FileInfo[] = [
  { id: '1', name: 'file1.txt', kind: 'document', /* ... */ },
  { id: '2', name: 'file2.jpg', kind: 'image', /* ... */ },
];

const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());

<FileGrid
  files={files}
  selectedIds={selectedIds}
  onFileClick={(file, event) => {
    // Handle selection logic
    console.log('Clicked:', file.name);
  }}
  onFileDoubleClick={(file) => {
    console.log('Open:', file.name);
  }}
/>

Features:

  • Responsive grid (2-6 columns based on viewport)
  • Checkbox selection (appears on hover)
  • Thumbnail display
  • Multi-select support via selectedIds

FileList

Table/list layout:

import { FileList } from '@spacedrive/explorer';

<FileList
  files={files}
  selectedIds={selectedIds}
  onFileClick={(file, event) => {}}
  onFileDoubleClick={(file) => {}}
  sort={{ field: 'name', direction: 'asc' }}
  onSort={(field) => {
    console.log('Sort by', field);
  }}
/>

Columns:

  • Name (always visible)
  • Size (hidden on mobile)
  • Modified date (hidden on mobile)
  • Kind (hidden on mobile)

Click column headers to sort.

FileRow

Single row (used internally by FileList):

import { FileRow } from '@spacedrive/explorer';

<FileRow
  file={file}
  selected={selectedIds.has(file.id)}
  onClick={() => {}}
  onDoubleClick={() => {}}
/>

Inspector Components

Inspector

File metadata panel:

import { Inspector } from '@spacedrive/explorer';

<Inspector
  file={file}
  tags={tags}
  onTagRemove={(tagId) => console.log('Remove tag', tagId)}
/>

Displays:

  • File name
  • Tags (with remove buttons if onTagRemove provided)
  • File metadata (kind, size, dates, path)
  • Custom metadata from file.metadata

InspectorPanel

Collapsible section for inspector:

import { InspectorPanel } from '@spacedrive/explorer';

<InspectorPanel title="EXIF Data" defaultOpen={true}>
  <dl>
    <dt>Camera</dt>
    <dd>iPhone 14 Pro</dd>
    <dt>Aperture</dt>
    <dd>f/1.78</dd>
  </dl>
</InspectorPanel>

Interaction Components

RenameInput

Inline file rename:

import { RenameInput } from '@spacedrive/explorer';

<RenameInput
  initialValue="old-filename.txt"
  onRename={(newName) => console.log('Rename to', newName)}
  onCancel={() => console.log('Cancelled')}
/>

Features:

  • Auto-focused and selected on mount
  • Enter to confirm
  • Escape to cancel
  • Validates extension preservation

DragOverlay

Visual feedback during drag:

import { DragOverlay } from '@spacedrive/explorer';

<DragOverlay files={draggedFiles} />

Shows:

  • Stack of thumbnails (up to 3)
  • Count badge (+N for more)
  • "X items" label

QuickPreview

Spacebar preview modal:

import { QuickPreview } from '@spacedrive/explorer';

<QuickPreview
  file={selectedFile}
  isOpen={previewOpen}
  onClose={() => setPreviewOpen(false)}
  onNext={() => selectNextFile()}
  onPrevious={() => selectPreviousFile()}
/>

Supports:

  • Images (with zoom)
  • Video (native controls)
  • Audio (native controls)
  • Other files (placeholder)

Keyboard shortcuts:

  • Escape: Close
  • Arrow keys: Navigate

Types

All components export their prop types:

import type {
  FileKind,
  FileInfo,
  TagInfo,
  ViewMode,
  SortField,
  SortDirection,
  SortState,
  SelectionState,
} from '@spacedrive/explorer';

FileKind

type FileKind = 
  | 'document'
  | 'image'
  | 'video'
  | 'audio'
  | 'archive'
  | 'executable'
  | 'code'
  | 'unknown';

FileInfo

interface FileInfo {
  id: string;
  name: string;
  path: string;
  kind: FileKind;
  size: number;
  modifiedAt: Date;
  createdAt: Date;
  thumbnailUrl?: string;
  isDirectory: boolean;
  extension?: string;
  tags?: string[];
  metadata?: Record<string, unknown>;
}

TagInfo

interface TagInfo {
  id: string;
  name: string;
  color: string;
}

Design Principles

  1. Data via props - Components don't fetch data
  2. Platform-agnostic - React DOM only, no platform APIs
  3. Virtual-scroll ready - Works with @tanstack/react-virtual
  4. Thumbnail contract - URL or kind identifier
  5. Callback-driven - Events via props, not internal state

Browser Support

  • Chrome/Edge 88+
  • Firefox 78+
  • Safari 14+

Requires:

  • CSS Grid
  • CSS Custom Properties
  • Intersection Observer (for virtual scrolling)

License

MIT © Spacedrive