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.
+1 more
onChange value0 selections
[]Installation
$Terminal
npx shadcn add https://raw.githubusercontent.com/sis-thesqd/squad-sdk/main/public/r/file-size-picker-block.jsonSetup
Configure environment variables and ensure the API route is in place.
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 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.
+1 more
onChange value0 selections
[]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.
+1 more
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.
Nothing to show here yet
+1 more
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`.
+1 more
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.
Nothing to show here yet
+1 more