Skip to content
ilinxa/pro-ui

Team Task Claim

alphav0.2.1

Task autonomy control — an open-for-anyone toggle, a volunteer claim button, and an assignee chip with neutral release and reassign.

Category: GamificationUpdated: 2026-08-11Created: 2026-07-01Author: ilinxa

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

Initialize shadcn (once per project)Seeds lib/utils.ts and components.json. Skip if you've already used any shadcn component.
pnpm dlx shadcn@latest init
Register the @ilinxa namespace (once per project)Add to your components.json. Merge with existing config.
"registries": {
  "@ilinxa": "https://ui.ilinxa.com/r/{name}.json"
}
Install the component
pnpm dlx shadcn@latest add @ilinxa/team-task-claim

Add -fixtures for dummy data:

pnpm dlx shadcn@latest add @ilinxa/team-task-claim-fixtures

Preview

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.

Open for anyone
TFAssigned to Theo Fischer
Assigned to Theo Fischer
Unassigned

Legal edge — assigned AND open

Resolves to claimed; the open-for-anyone flag stays visible and toggleable alongside the chip.

ABAssigned to Ama Boateng
Assigned to Ama Boateng

Density — compact (kanban) vs comfortable (rich card)

Compact collapses labels to icons + truncates the assignee name (avatar floor).

TFAssigned to Theo Fischer
Assigned to Theo Fischer
TFAssigned to Theo Fischer
Assigned to Theo Fischer

Read-only (display-only)

Omit the callbacks (or pass readOnly) → all actions hide; the state still shows. No dead buttons.

TFAssigned to Theo Fischer
Assigned to Theo Fischer

À-la-carte sub-parts (no assembly)

Each flat export mounts standalone — the toggle, the claim action, and the assignee chip on their own.

U-Assigned to u-ghost

Demo source

demo.tsxtsx

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. value drives 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. onEvent emits task-claim.interaction on meaningful interactions only (toggle / claim / reassign / release) — not on render.
  • Portable. No next/*, no other registry import; own types.ts slice; only the shadcn switch / button / avatar / popover / command primitives.

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

Tags

team-task-claimgamificationautonomyassignmentvolunteerteamcooperativetelemetry

Dependencies

shadcn primitives: switch, button, avatar, popover, command
npm peer deps: lucide-react@^1.11.0