File size picker

Multi-select picker for design output sizes with a filter chip bar for Recommended / My File Sizes / My Kits / All. Fetches from a Supabase-backed API route scoped to the given account. The All tab paginates upstream 60 rows at a time and forwards the search box to the API's `query` param so it scales to thousands of rows without loading the whole catalog. Optionally narrow the catalog to a `department` (`design` | `video` | `social` | `web` | `brand`) and/or a list of `projectType` IDs. Selections snapshot the full file-size metadata (dims, fold type, bleed) and folded print sizes annotate their dimensions as unfolded with a bleed badge. Pass `kits` to surface named bundles as expandable Kit cards above the individual sizes on the Recommended tab — "Add kit" selects every member size at once.

Kits
Last used
Sermon Series
  • Title Slide1920 × 1080 px
  • Background Only1920 × 1080 px
  • Social Square1080 × 1080 px
  • Web Banner1440 × 810 px
Individual sizes
onChange value0 selections
[]
Installation
$Terminal
npx shadcn add https://raw.githubusercontent.com/sis-thesqd/squad-sdk/main/public/r/file-size-picker-block.json
Setup
Configure environment variables and ensure the API route is in place (v1 only — v2 is self-contained).
TXTSetup
## Setup

1. Set the following environment variable in your `.env.local`:
   - `SQUAD_API_KEY` — your Squad API key (server-only). Used to call `https://api.thesqd.com/v1/prf/file-sizes`. Generate one at [sdk.thesqd.com/settings/api-keys](https://sdk.thesqd.com/settings/api-keys) (employees only).

2. The CLI install drops three Next.js route handlers under `app/api/file-sizes/` that proxy the Squad API with `Authorization: Bearer ${SQUAD_API_KEY}`:

   | File | Method | Squad API endpoint | Purpose |
   |------|--------|---------------------|---------|
   | `route.ts`                         | `GET`   | `GET /v1/prf/file-sizes`                       | List rows. Forwards `account`, `department`, repeating `project_type`, `query`, `recommended`, `list_defaults`, `limit`, and `offset` upstream; returns `{ rows, total }`. Rows include `foldType` (`tri-fold` / `bi-fold` / `no-fold` / `custom`) and `bleedSize` (`0.125` / `0.25`) when set upstream. The block uses `limit=60` + `offset` for the All tab, `recommended=true` for the Recommended tab, `list_defaults=false` for My File Sizes, and a single un-paginated catalog fetch for Kits. |
   | `route.ts`                         | `POST`  | `POST /v1/prf/file-sizes`                      | Create a new file size. The block sends `account: <currentAccount>` so the row is owned, not global. |
   | `[id]/route.ts`                    | `PATCH` | `PATCH /v1/prf/file-sizes/{id}`                | Edit an account-owned row. |
   | `[id]/visibility/route.ts`         | `PATCH` | `PATCH /v1/prf/file-sizes/{id}/visibility`     | Hide a row for the current account (the soft-delete — Squad API has no DELETE). |

3. **Editing a global row** (`account === null`) is special: the block POSTs a new account-owned copy with the user's edits applied instead of mutating the global. That keeps the global record intact for everyone else while still feeling like an in-place edit to the user.
Usage
Drop in the demo component or wire up the picker with your own state.
TSImport
import { FileSizePicker, type FileSize, type FileSizeKit, type FileSizeSelection } from "@/components/blocks/file-size-picker-block";
TSExample
"use client";

import * as React from "react";
import {
  FileSizePicker,
  type FileSizeSelection,
} from "@/components/blocks/file-size-picker-block";

export default function Page() {
  // `account` scopes the picker to the customer's own file sizes.
  // Pass `readOnly` to skip mutating fetches (useful in docs/demos).
  // Pass `value` + `onChange` to control selection externally; omit both
  // to let the picker manage its own selection state.
  //
  // Use `department` ("design" | "video" | "social" | "web" | "brand")
  // and/or `projectType` (integer[]) to narrow the catalog to file sizes
  // linked to those project types upstream.
  //
  // Each selection snapshots the full file-size metadata at selection time
  // (`{ id, label, width, height, unit, foldType, bleedSize, ... }`) so
  // downstream consumers (review pages, submissions, cross-field logic)
  // can render and branch on it without re-fetching the catalog.
  //
  // In a dynamic form, gate the initial fetches on the form's prefetch
  // flag via `scopeReady` (defaults to true for standalone usage).
  const [selected, setSelected] = React.useState<FileSizeSelection[]>([]);
  return (
    <FileSizePicker
      account={3728}
      department="design"
      projectType={[1, 7]}
      value={selected}
      onChange={setSelected}
    />
  );
}
Default
Full grid of all account and global file sizes with Recommended / My File Sizes / My Kits / All filter chips and a removable-badge summary. Each tile shows a Printer icon for print sizes and a Monitor icon for digital ones.
Kits
Last used
Sermon Series
  • Title Slide1920 × 1080 px
  • Background Only1920 × 1080 px
  • Social Square1080 × 1080 px
  • Web Banner1440 × 810 px
Individual sizes
onChange value0 selections
[]
Empty state
When the catalog (or the active tab / search) matches nothing, the grid shows a compact `<Empty size="sm">` with a flat muted `FoldersIcon` instead of a bare line of text.

No file sizes here

Nothing matches this view yet. Try another tab or search, or create a new file size.

Catalog-scoped Type chips (print-only)
The "New file size" modal scopes its Print / Digital chips to the types present in the currently shown options. This catalog only contains print sizes, so opening the + modal disables Digital and defaults the chip to Print — and vice versa for a digital-only catalog. Both stay enabled when the catalog mixes types. After creating, the new size is auto-selected and the picker jumps to My File Sizes.
Add file size in a popover
Pass `createIn="popover"` to anchor the "New file size" form to the + button as a popover instead of a centered modal — it closes on outside-click / Esc. The form, validation, and save behavior are identical; only the surface differs. Editing an existing size still opens the modal.

No file sizes here

Nothing matches this view yet. Try another tab or search, or create a new file size.

Add file size modal (animated open)
Click the + button in the header to open the "New file size" modal. It ships with the transitions-dev "Modal open / close" animation (06-modal.md): the dialog scales up from `--modal-scale` with a soft cross-fade on open and dips back down on close. The animation rides on the Dialog's `.t-modal` class in the shipped `squad-ui.css`, adapted to Base UI's `data-starting-style` / `data-ending-style` transition contract, so any account-scoped picker gets it for free. It honors `prefers-reduced-motion`.
Kits
Last used
Sermon Series
  • Title Slide1920 × 1080 px
  • Background Only1920 × 1080 px
  • Social Square1080 × 1080 px
  • Web Banner1440 × 810 px
Individual sizes
onChange value0 selections
[]
Selection limits (min / max)
Pass `minSelections` (defaults to 1) and an optional `maxSelections`. The cap is a rolling window: picking a new card at the max swaps out the oldest selection instead of being blocked — with a cap of 1, clicking another card simply switches the selection. Here the cap is 3.

No file sizes here

Nothing matches this view yet. Try another tab or search, or create a new file size.