File chip
What this solves
Visual and self-explaining action container. Mid-truncation keeps the start and the extension so two long exports from the same vendor still read apart, and the small ↗ glyph tells a reader the chip opens something before they click it.
Use it when
- A file attached to a job or a scrape run.Speedway's FileChip (file-chip.tsx:33-72), opening a shared preview host.
- A schema or export file named inline in a table cell.Any row where a filename doubles as a naming label rather than a value.
Rendered
Fixture data; check both themes.Clickable, opens a preview
Plain, nothing to open
2026-08-light…ll-export.xml
Click a file chip to open its preview.
patterns/file-chip/example.tsxCopy this into a fresh route and it renders as above.
"use client";import { useState } from "react";import { FieldChip, RenderIcon } from "@versable-git/ui";// Mid-truncation keeps the start and the extension; the middle elides, so// two long exports from the same vendor still read differently. Clicking a// clickable chip opens it below, standing in for the real shared preview host.function midTruncate(name: string, max = 28): string { if (name.length <= max) return name; const keep = Math.floor((max - 1) / 2); return `${name.slice(0, keep)}…${name.slice(-keep)}`;}function FileChip({ filename, clickable, opened, onOpen,}: { filename: string; clickable?: boolean; opened?: boolean; onOpen?: (name: string) => void;}) { return ( <FieldChip tone="file" onClick={clickable ? () => onOpen?.(filename) : undefined} className={ opened ? "bg-warning/15 text-warning ring-warning/25 w-fit max-w-full whitespace-nowrap" : "bg-base-200 text-base-content/70 ring-base-300 w-fit max-w-full whitespace-nowrap" } > <span className="inline-flex items-center gap-1"> <RenderIcon Icon="FileSheet" size={12} className="shrink-0" /> <span className="font-mono text-xs">{midTruncate(filename)}</span> {clickable ? <span className="text-base-content/65 text-[10px]">↗</span> : null} </span> </FieldChip> );}export function FileChips() { const [opened, setOpened] = useState<string | null>(null); return ( <div className="flex flex-col gap-6"> <div className="flex flex-col gap-2"> <span className="text-base-content/45 text-xs font-medium tracking-wide uppercase">Clickable, opens a preview</span> <div className="flex flex-wrap gap-2"> <FileChip clickable filename="brake-pads-q3-vendor-export.xlsx" opened={opened === "brake-pads-q3-vendor-export.xlsx"} onOpen={setOpened} /> <FileChip clickable filename="fitment.csv" opened={opened === "fitment.csv"} onOpen={setOpened} /> </div> </div> <div className="flex flex-col gap-2"> <span className="text-base-content/45 text-xs font-medium tracking-wide uppercase">Plain, nothing to open</span> <div className="flex flex-wrap gap-2"> <FileChip filename="2026-08-lighting-catalog-full-export.xml" /> </div> </div> <div className="text-base-content/65 text-xs"> {opened != null ? ( <> Preview open: <span className="font-mono">{opened}</span> </> ) : ( "Click a file chip to open its preview." )} </div> </div> );}Where it ships
speedway/app/components/file-chip.tsx:33-72FieldChip tone=file with a file-sheet icon, mid-truncated name, and an external-link glyph; quiet gray by owner rulingspeedway/app/components/file-chip.tsx:77-80the mid-truncation helper: keep the start and the extension, elide the middle
App-specific: The real chip opens a shared preview host through a hook (one host mounted in the shell, many chip triggers), and degrades to a plain chip when there is no source id to open. Clicking never navigates.