Files

Utilities for file type detection, categorization, and formatting.

Related component

For displaying file type icons, see MimeTypeIcon.

Overview

The file utilities provide a unified API for working with file types. All functions accept either a filename (uses extension) or MIME type string, making them flexible for different use cases.

File type detection and categorization live in utilities, while display formatting functions live in utilities/formatters:

// Detection, categorization, and type guards
import {
  getFileInfo,
  getFileCategory,
  getFileExtension,
  isImage,
  isPreviewable,
  FILE_CATEGORIES,
} from "@tilt-legal/cubitt-components/utilities";

// Display formatting
import {
  getFileLabel,
  getExtensionLabel,
} from "@tilt-legal/cubitt-components/utilities/formatters";

Main API

getFileInfo

Returns complete file information in a single call. Use this when you need multiple pieces of information about a file.

import { getFileInfo } from "@tilt-legal/cubitt-components/utilities";

getFileInfo("document.pdf");
// { category: "document", label: "PDF Document", extension: "pdf" }

getFileInfo("photo.png", "image/png");
// { category: "image", label: "PNG Image", extension: "png" }

getFileInfo(undefined, "application/pdf");
// { category: "document", label: "PDF Document", extension: "" }
PropertyTypeDescription
categoryFileCategoryCategory for grouping (e.g., "image")
labelstringHuman-readable label (e.g., "PNG Image")
extensionstringLowercase extension without dot, or empty

File details component

function FileDetails({ file }: { file: File }) {
  const { category, label, extension } = getFileInfo(file.name, file.type);

  return (
    <div className="flex items-center gap-3">
      <MimeTypeIcon mimeType={file.type} className="size-8" />
      <div>
        <p className="font-medium">{file.name}</p>
        <p className="text-sm text-muted-foreground">
          {label}{" "}
          {extension && (
            <Badge variant="secondary" appearance="outline">{extension.toUpperCase()}</Badge>
          )}
        </p>
      </div>
    </div>
  );
}

Accessor Functions

getFileCategory

Returns the file category for grouping and filtering.

import { getFileCategory } from "@tilt-legal/cubitt-components/utilities";

getFileCategory("photo.png"); // "image"
getFileCategory("application/pdf"); // "document"
getFileCategory("document.docx"); // "document"
getFileCategory("video/mp4"); // "video"
getFileCategory("unknown-file"); // "unknown"
CategoryExtensions
imagepng, jpg, gif, svg, webp, heic, etc
videomp4, mov, avi, webm, mkv, etc
audiomp3, wav, ogg, flac, aac, etc
documentpdf, doc, docx, odt, rtf
spreadsheetxls, xlsx, csv, ods
presentationppt, pptx, odp
archivezip, rar, 7z, tar, gz
codejs, ts, py, java, json, xml, etc
texttxt, md, html
emaileml, msg
unknownUnrecognized types

getFileLabel

Returns a human-readable description suitable for display.

import { getFileLabel } from "@tilt-legal/cubitt-components/utilities/formatters";

getFileLabel("photo.png"); // "PNG Image"
getFileLabel("application/pdf"); // "PDF Document"
getFileLabel("document.docx"); // "Word Document"
getFileLabel("data.xlsx"); // "Excel Spreadsheet"

With MimeTypeIcon

<div className="flex items-center gap-2">
  <MimeTypeIcon mimeType={file.type} className="size-5" />
  <span className="text-muted-foreground">
    {getFileLabel(file.name, file.type)}
  </span>
</div>
// Displays: [icon] "PDF Document"

getFileExtension

Extracts the file extension from a filename. Returns lowercase without the leading dot.

import { getFileExtension } from "@tilt-legal/cubitt-components/utilities";

getFileExtension("document.pdf"); // "pdf"
getFileExtension("photo.JPEG"); // "jpeg"
getFileExtension("archive.tar.gz"); // "gz"
getFileExtension(".gitignore"); // "gitignore"
getFileExtension("README"); // ""

getExtensionLabel

Returns an uppercase extension label for badges and compact displays. Falls back to MIME type if filename has no extension.

import { getExtensionLabel } from "@tilt-legal/cubitt-components/utilities/formatters";

getExtensionLabel("document.pdf"); // "PDF"
getExtensionLabel(undefined, "image/png"); // "PNG"
getExtensionLabel("file", "application/pdf"); // "PDF"

Extension badge

const ext = getExtensionLabel(file.name, file.type);

{
  ext && (
    <Badge variant="secondary" appearance="outline" className="text-xs">
      {ext}
    </Badge>
  );
}

resolveExtension

Resolves a filename and/or MIME type to a canonical file extension. Tries the filename extension first, then resolves the MIME type through the internal registry.

import { resolveExtension } from "@tilt-legal/cubitt-components/utilities";

resolveExtension("report.pdf"); // "pdf"
resolveExtension(undefined, "audio/x-m4a"); // "m4a"
resolveExtension("file.bin", "application/pdf"); // "bin"
resolveExtension(undefined, "application/octet-stream"); // ""

Type Guards

Functions for checking file types. All accept string | null | undefined and return boolean.

isImage, isVideo, isAudio

Check if MIME type matches a specific media category.

import { isImage, isVideo, isAudio } from "@tilt-legal/cubitt-components/utilities";

isImage("image/png"); // true
isImage("video/mp4"); // false

isVideo("video/mp4"); // true
isAudio("audio/mpeg"); // true

isMedia

Returns true if the MIME type is image, video, or audio.

import { isMedia } from "@tilt-legal/cubitt-components/utilities";

isMedia("image/png"); // true
isMedia("video/mp4"); // true
isMedia("audio/mpeg"); // true
isMedia("application/pdf"); // false

isPreviewable

Returns true if the file can be previewed in a browser (images, videos, PDFs).

import { isPreviewable } from "@tilt-legal/cubitt-components/utilities";

isPreviewable("image/png"); // true
isPreviewable("video/mp4"); // true
isPreviewable("application/pdf"); // true
isPreviewable("text/plain"); // false

Conditional preview

{
  isPreviewable(file.type) ? (
    <FilePreview file={file} />
  ) : (
    <FileDownloadButton file={file} />
  );
}

fileIs

Namespaced object containing all type guards.

import { fileIs } from "@tilt-legal/cubitt-components/utilities";

fileIs.image("image/png"); // true
fileIs.video("video/mp4"); // true
fileIs.previewable("image/png"); // true

Constants

FILE_CATEGORIES

Readonly array of all available file categories. Useful for building filter UIs or validation.

import { FILE_CATEGORIES } from "@tilt-legal/cubitt-components/utilities";

// ["image", "video", "audio", "document", "spreadsheet", "presentation", "archive", "code", "text", "email", "unknown"]

Filter dropdown

const fileCategoryItems = FILE_CATEGORIES.filter((category) => category !== "unknown").map(
  (category) => ({
    label: `${category.charAt(0).toUpperCase() + category.slice(1)}s`,
    value: category,
  })
);

<Select
  items={[{ label: "All files", value: "all" }, ...fileCategoryItems]}
  value={filter}
  onValueChange={setFilter}
>
  <SelectTrigger>
    <SelectValue placeholder="All files" />
  </SelectTrigger>
  <SelectContent>
    <SelectItem value="all">All files</SelectItem>
    {fileCategoryItems.map((item) => (
      <SelectItem key={item.value} value={item.value}>
        {item.label}
      </SelectItem>
    ))}
  </SelectContent>
</Select>

Types

FileCategory

Union type of all possible file categories.

import type { FileCategory } from "@tilt-legal/cubitt-components/utilities";

type FileCategory =
  | "image"
  | "video"
  | "audio"
  | "document"
  | "spreadsheet"
  | "presentation"
  | "archive"
  | "code"
  | "text"
  | "email"
  | "unknown";

FileInfo

Return type of getFileInfo().

import type { FileInfo } from "@tilt-legal/cubitt-components/utilities";

type FileInfo = {
  category: FileCategory;
  label: string;
  extension: string;
};

Examples

File list with categories

function FileList({ files }: { files: File[] }) {
  return (
    <div className="space-y-2">
      {files.map((file) => (
        <div key={file.name} className="flex items-center gap-3">
          <MimeTypeIcon mimeType={file.type} className="size-5" />
          <span className="flex-1 truncate">{file.name}</span>
          <Badge variant="secondary">{getFileCategory(file.type)}</Badge>
        </div>
      ))}
    </div>
  );
}

Grouped file browser

function GroupedFiles({ files }: { files: File[] }) {
  const grouped = files.reduce(
    (acc, file) => {
      const category = getFileCategory(file.name, file.type);
      acc[category] = [...(acc[category] || []), file];
      return acc;
    },
    {} as Record<FileCategory, File[]>,
  );

  return (
    <div className="space-y-6">
      {Object.entries(grouped).map(([category, files]) => (
        <div key={category}>
          <h3 className="font-medium capitalize mb-2">{category}s</h3>
          <div className="space-y-1">
            {files.map((file) => (
              <FileRow key={file.name} file={file} />
            ))}
          </div>
        </div>
      ))}
    </div>
  );
}

File details sheet

function FileDetailsSheet({ file }: { file: FileWithMeta }) {
  const info = getFileInfo(file.name, file.mimeType);

  return (
    <SheetContent>
      <SheetHeader>
        <SheetTitle>{file.name}</SheetTitle>
      </SheetHeader>
      <div className="space-y-4 py-4">
        <div className="flex items-center gap-4">
          <MimeTypeIcon mimeType={file.mimeType} className="size-12" />
          <div>
            <p className="font-medium">{info.label}</p>
            <p className="text-sm text-muted-foreground">
              {formatBytes(file.size)}
            </p>
          </div>
        </div>

        {isPreviewable(file.mimeType) && <FilePreview file={file} />}
      </div>
    </SheetContent>
  );
}
function MediaGallery({ files }: { files: FileWithMeta[] }) {
  const [filter, setFilter] = useState<"all" | "image" | "video">("all");

  const filtered = files.filter((file) => {
    if (filter === "all") return isMedia(file.mimeType);
    if (filter === "image") return isImage(file.mimeType);
    if (filter === "video") return isVideo(file.mimeType);
    return false;
  });

  return (
    <div>
      <ToggleGroup
        type="single"
        value={filter}
        onValueChange={(v) => setFilter(v as typeof filter)}
      >
        <ToggleGroupItem value="all">All</ToggleGroupItem>
        <ToggleGroupItem value="image">Images</ToggleGroupItem>
        <ToggleGroupItem value="video">Videos</ToggleGroupItem>
      </ToggleGroup>

      <div className="grid grid-cols-3 gap-2 mt-4">
        {filtered.map((file) => (
          <MediaThumbnail key={file.id} file={file} />
        ))}
      </div>
    </div>
  );
}

On this page