Team Task Claim
alphav0.2.1Task autonomy control — an open-for-anyone toggle, a volunteer claim button, and an assignee chip with neutral release and reassign.
Context
The Autonomy surface of the gamification pack (E4). Renders three states — open / claimed / unassigned (plus the legal assigned-and-open edge) — deterministically from a controlled TaskClaimState slice + this team's members. A member can open a task for anyone or volunteer for it by their own choice; the release/reassign path is a neutral, no-penalty transition (never destructive/red, never 'Drop'/'Abandon', never a penalty glyph or motion). Ships as a single-unit control (NOT a Root/context compound — nothing cross-cutting to hold, and D-06 forbids requiring a provider): flat à-la-carte sub-parts (OpenForAnyoneToggle, ClaimButton, AssigneeChip) under a logic-free TeamTaskClaim assembly, so a host can drop just the toggle or just the chip. The reassign picker is popover + command (searchable, keyboard-navigable, team-scoped). Emits task-claim.interaction via onEvent on meaningful interactions only. Controlled (D-06); capability-gated (omit a callback → that affordance hides); readOnly for display-only. Portable: zero next/*, own types.ts slice, imports no other registry component; SSR-safe. Fifth component of the gamification-system.
Installation
pnpm dlx shadcn@latest init"registries": {
"@ilinxa": "https://ui.ilinxa.com/r/{name}.json"
}pnpm dlx shadcn@latest add @ilinxa/team-task-claimAdd -fixtures for dummy data:
pnpm dlx shadcn@latest add @ilinxa/team-task-claim-fixturesPreview
Three states — live
Open (invite + I'll take this) · Claimed (chip + neutral Release + Reassign) · Unassigned (volunteer + open together). Try claiming, releasing, reassigning — nothing penalizes anyone.
Legal edge — assigned AND open
Resolves to claimed; the open-for-anyone flag stays visible and toggleable alongside the chip.
Density — compact (kanban) vs comfortable (rich card)
Compact collapses labels to icons + truncates the assignee name (avatar floor).
Read-only (display-only)
Omit the callbacks (or pass readOnly) → all actions hide; the state still shows. No dead buttons.
À-la-carte sub-parts (no assembly)
Each flat export mounts standalone — the toggle, the claim action, and the assignee chip on their own.
Demo source
Usage
When to use
Drop TeamTaskClaim onto any team task card to give members an autonomy affordance: open a task for anyone, or volunteer for it — by their own choice, never told to. It is the E4 / Autonomy surface of the gamification pack. The rule that shapes it: choice is always available, never forced; releasing or reassigning never penalizes whoever held it before.
Basic example
import { TeamTaskClaim } from "@/components/team-task-claim"
<TeamTaskClaim
teamId={team.id}
members={team.members} // this team only
value={task.choice} // { taskId, openForAnyone, assigneeId }
currentMemberId={viewerId}
onOpenForAnyoneChange={(open) => update(task.id, { openForAnyone: open })}
onClaim={(id) => update(task.id, { assigneeId: id, openForAnyone: false })}
onAssigneeChange={(id) => update(task.id, { assigneeId: id })} // undefined = release
density="compact"
/>Three states
- Open — a friendly 🙌 Open-for-anyone invite plus an I'll take this claim.
- Claimed — an assignee chip + a neutral Release + a reassign picker. Release is
onAssigneeChange(undefined)— never a penalty. - Unassigned — Volunteer and Open-for-anyone offered side by side.
Capability-gating & read-only
Each affordance hides when its callback is omitted; readOnly is a global off-switch. They compose — omitting all callbacks produces the same display-only render as readOnly. Prefer readOnly to express intent.
À-la-carte parts
import {
OpenForAnyoneToggle, ClaimButton, AssigneeChip,
} from "@/components/team-task-claim"
// Mount just the part you need — each is flat-exported, no Root/context.
<OpenForAnyoneToggle open={task.choice.openForAnyone} onOpenChange={setOpen} />Notes
- Controlled.
valuedrives the render; the component holds no source-of-truth choice state. - Team-scoped. Only
members(this team) appear in the reassign picker; nothing public/inter-team. - Telemetry.
onEventemitstask-claim.interactionon meaningful interactions only (toggle / claim / reassign / release) — not on render. - Portable. No
next/*, no other registry import; owntypes.tsslice; only the shadcnswitch/button/avatar/popover/commandprimitives.
Features
- Three states — open (invite + 'I'll take this') / claimed (chip + neutral Release + Reassign) / unassigned (volunteer + open together)
- Never-forced by construction — no mandatory/locked state; choice is the default (readOnly is opt-in)
- No-penalty release — folded into onAssigneeChange(undefined); never destructive/red, never a cold verb or penalty motion
- Reassign via a searchable, keyboard-navigable, team-scoped popover + command picker
- Controlled + capability-gated — value drives render; omit a callback and that affordance hides; no dead buttons
- Telemetry — task-claim.interaction on meaningful interactions only (toggle / claim / reassign / release)
- Single-unit with flat à-la-carte parts — mount just OpenForAnyoneToggle, ClaimButton, or AssigneeChip; no Root/context