Concept approval

Review workspace for a project's concepts — a rail of deliverables, rounds and options as segment tabs, compare and full screen, the artwork as the hero, and a footer with the round's outcome and the decisions.

Connect-Info Card
Back
Waiting on youSent Mar 4Address updated on the back.
Installation
$Terminal
npx shadcn@latest add https://sdk-components.thesqd.com/r/concept-approval.json
Usage
Pass `files` for the default image/video stage, or `children` to draw your own (an annotated canvas, a comparison).
TSImport
import { ConceptApproval } from "@/components/blocks/concept-approval";
TSExample
import { ConceptApproval } from "@/components/blocks/concept-approval";
import { AnnotatedCanvas } from "@/components/custom/annotated-canvas";

// Your own stage: pass children instead of files. The block keeps the rail,
// the round/option tabs, compare, full screen and the footer.
export function ReviewPage() {
  return (
    <ConceptApproval
      items={deliverables}
      rounds={rounds}
      options={[{ id: "c1", label: "Concept 1", badge: { label: "Chosen", tone: "open" } }]}
      statuses={[{ label: "2 unresolved", tone: "changes" }]}
      meta="Sent Mar 4"
    >
      <AnnotatedCanvas src={src} pins={pins} onPlace={place} />
    </ConceptApproval>
  );
}
Composition
One ConceptApproval component: a rail, a toolbar, a stage slot and a footer — all Squad SDK primitives.
ConceptApproval                  — review workspace for a project's concepts
├── rail (items, 1+)             — Item list: thumbnail, name, status dot + line
└── card                         — card-surface
    ├── toolbar
    │   ├── rounds               — Tabs (segment), a status dot per round
    │   ├── toolbar slot
    │   ├── Compare rounds       — Button, pressed while comparing
    │   └── Full screen          — icon Button → Dialog with the stage
    ├── stage
    │   ├── options (2+)         — Tabs (segment) above the artwork, optional xxs Badge
    │   └── children, or the active option's file
    └── footer
        ├── statuses + meta + note — left; badge first
        └── comments link + actions[] — right

While comparing, pass the shared <CompareSlider> (components/ui/compare-slider)
as the stage: before/after split by a draggable line, arrow keys step it.
Preview
Three deliverables in the rail. Each round is a tab with its status dot; a round with several options gets its own tabs above the artwork. Compare and full screen sit on the toolbar, the round's outcome and the decisions in the footer.
Connect-Info Card
Back
Waiting on youSent Mar 4Address updated on the back.
One deliverable
With one item the rail is hidden and the card takes the full width.
Connect-Info Card
Back
Waiting on youSent Mar 4Address updated on the back.
API Reference
Props for the block plus the shape of each entry it takes.

ConceptApproval

PropTypeDefaultDescription
itemsConceptApprovalItem[]—The left rail, shown for one item too. Omit to hide it.
activeItemId / onItemChangestring / (id) => void—Controlled rail selection. Uncontrolled when omitted.
roundsConceptApprovalRound[]—The open deliverable's rounds, oldest first — one segment tab each.
round / onRoundChangenumber / (round) => void—Controlled round. Defaults to the last one.
optionsConceptApprovalOption[]—The open round's options. Defaults to one per file; tabs show for 2+.
activeOptionId / onOptionChangestring / (id) => void—Controlled option.
filesConceptApprovalFile[]—The default stage draws the active option's image or video.
childrenReact.ReactNode—Your own stage (an annotated canvas, a comparison) in place of `files`.
fullscreenReact.ReactNode | false—Full-screen dialog content. Defaults to the stage; `false` hides the button.
toolbarReact.ReactNode—Extra toolbar controls, before Compare.
onCompare() => void—Shows the Compare rounds button.
comparingbooleanfalsePressed state of Compare.
compareDisabledbooleanfalseDisable Compare (e.g. on the first round).
compareLabel / stopComparingLabelstring"Compare rounds" / "Stop comparing"
statusesConceptApprovalStatus[]—xs status badges, first on the footer's left.
metaReact.ReactNode—Muted text after the badges ("Sent Mar 4").
noteReact.ReactNode—One line after the badges and meta, on the left.
actionsConceptApprovalAction[]—Footer buttons. Icon-only sizes get a Tooltip.
commentsLabel / onCommentsClickReactNode / () => void—Footer link, shown when both are passed.
onFullscreenChange(open: boolean) => void—The full-screen dialog opened or closed — e.g. to move something floating into it.
spotlightbooleanfalseDim and blur the whole screen except the card (the rail dims too) — e.g. while writing about a spot on the work. Uses <Spotlight> (components/ui/spotlight).
spotlightKeep() => Element[]—More elements to leave lit under the spotlight, such as a comments panel.
onSpotlightDismiss() => void—A click on the dimmed area.
railLabel / fullscreenLabelstring"Deliverables" / "Open full screen"
classNamestring—Merged onto the outer section.

ConceptApprovalItem

PropTypeDefaultDescription
idstring—
nameReact.ReactNode—
thumbnailstring—Drawn contained, never cropped.
statusReact.ReactNode—One line under the name.
toneConceptApprovalTone—Colour of the status dot.

ConceptApprovalRound

PropTypeDefaultDescription
valuenumber—
labelReact.ReactNode"Round {value}"
toneConceptApprovalTone—Colour of the tab's dot.

ConceptApprovalOption

PropTypeDefaultDescription
idstring—
labelReact.ReactNode—
badge{ label, tone }—xxs badge after the label ("Approved", "Chosen").

ConceptApprovalFile

PropTypeDefaultDescription
idstring—Matches an option id.
kind"image" | "video""image"
srcstring—
posterstring—Poster image for videos.
labelstring—Option label when `options` is not passed.
altstring—

ConceptApprovalStatus

PropTypeDefaultDescription
labelReact.ReactNode—
tone"approved" | "changes" | "open" | "unused"—success / warning / brand / muted.

ConceptApprovalAction

PropTypeDefaultDescription
idstring—
labelstring—Button label, or the tooltip on an icon-only size.
iconReact.ReactNode—
onClick() => void—
variantButtonProps["variant"]"outline"
sizeButtonProps["size"]"sm"