

<Callout type="info" title="Related component">
  For displaying file type icons, see [MimeTypeIcon](/primitives/mime-type).
</Callout>

## Overview [#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`:

```tsx
// 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 [#main-api]

### `getFileInfo` [#getfileinfo]

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

```tsx
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: "" }
```

| Property    | Type           | Description                               |
| ----------- | -------------- | ----------------------------------------- |
| `category`  | `FileCategory` | Category for grouping (e.g., "image")     |
| `label`     | `string`       | Human-readable label (e.g., "PNG Image")  |
| `extension` | `string`       | Lowercase extension without dot, or empty |

#### File details component [#file-details-component]

```tsx
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 [#accessor-functions]

### `getFileCategory` [#getfilecategory]

Returns the file category for grouping and filtering.

```tsx
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"
```

| Category       | Extensions                          |
| -------------- | ----------------------------------- |
| `image`        | png, jpg, gif, svg, webp, heic, etc |
| `video`        | mp4, mov, avi, webm, mkv, etc       |
| `audio`        | mp3, wav, ogg, flac, aac, etc       |
| `document`     | pdf, doc, docx, odt, rtf            |
| `spreadsheet`  | xls, xlsx, csv, ods                 |
| `presentation` | ppt, pptx, odp                      |
| `archive`      | zip, rar, 7z, tar, gz               |
| `code`         | js, ts, py, java, json, xml, etc    |
| `text`         | txt, md, html                       |
| `email`        | eml, msg                            |
| `unknown`      | Unrecognized types                  |

***

### `getFileLabel` [#getfilelabel]

Returns a human-readable description suitable for display.

```tsx
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 [#with-mimetypeicon]

```tsx
<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` [#getfileextension]

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

```tsx
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` [#getextensionlabel]

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

```tsx
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 [#extension-badge]

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

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

***

### `resolveExtension` [#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.

```tsx
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 [#type-guards]

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

### `isImage`, `isVideo`, `isAudio` [#isimage-isvideo-isaudio]

Check if MIME type matches a specific media category.

```tsx
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` [#ismedia]

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

```tsx
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` [#ispreviewable]

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

```tsx
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 [#conditional-preview]

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

### `fileIs` [#fileis]

Namespaced object containing all type guards.

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

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

***

## Constants [#constants]

### `FILE_CATEGORIES` [#file_categories]

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

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

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

#### Filter dropdown [#filter-dropdown]

```tsx
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 [#types]

### `FileCategory` [#filecategory]

Union type of all possible file categories.

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

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

### `FileInfo` [#fileinfo]

Return type of `getFileInfo()`.

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

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

***

## Examples [#examples]

### File list with categories [#file-list-with-categories]

```tsx
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 [#grouped-file-browser]

```tsx
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 [#file-details-sheet]

```tsx
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>
  );
}
```

### Media gallery filter [#media-gallery-filter]

```tsx
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>
  );
}
```
