Components
Task
A single collapsible agent task card with a live status pill. Purely presentational — no Morphos equivalent.
Installation
npx kosmesis add taskpnpm dlx kosmesis add taskyarn dlx kosmesis add taskbunx kosmesis add taskInstall the following dependencies:
npm install @morphos/iconspnpm add @morphos/iconsyarn add @morphos/iconsbun add @morphos/iconsCopy and paste the following code into your project.
import { StatefulComponent } from "@praxisjs/core";import { Component, Prop, State } from "@praxisjs/decorators";import type { Children } from "@praxisjs/shared";import { Icon } from "@morphos/icons";import { cn } from "@/lib/utils";export type TaskStatus = "pending" | "running" | "done" | "error";export interface TaskProps { title: string; status?: TaskStatus; defaultOpen?: boolean; class?: string; children?: Children;}@Component()export class Task extends StatefulComponent { @Prop() title = ""; @Prop() status: TaskStatus = "pending"; @Prop() defaultOpen = false; @Prop() class?: string; @Prop() children?: Children; @State() _open = false; onBeforeMount() { this._open = this.defaultOpen; } toggle(): void { this._open = !this._open; } render() { return ( <div data-slot="task" data-status={() => this.status} class={cn("rounded-lg border bg-card text-card-foreground", this.class)}> <button type="button" class="flex w-full items-center gap-2 px-3 py-2 text-sm" onClick={() => { this.toggle(); }}> <span class={() => cn("inline-block shrink-0 transition-transform", this._open && "rotate-90")}> <Icon name="ChevronRight" size={14} /> </span> <span class={cn( "flex size-4 shrink-0 items-center justify-center rounded-full text-[10px] leading-none", "in-data-[status=done]:bg-primary in-data-[status=done]:text-primary-foreground", "in-data-[status=running]:animate-pulse in-data-[status=running]:bg-primary/30", "in-data-[status=pending]:bg-muted", "in-data-[status=error]:bg-destructive in-data-[status=error]:text-destructive-foreground", )} > {() => this.status === "done" ? ( <Icon name="Check" size={10} /> ) : this.status === "error" ? ( <Icon name="X" size={10} /> ) : ( "" ) } </span> <span class="flex-1 text-left font-medium">{this.title}</span> </button> <div data-state={() => (this._open ? "open" : "closed")} class="flex flex-col gap-1.5 px-3 pb-3 pl-9 text-xs text-muted-foreground data-[state=closed]:hidden" > {this.children} </div> </div> ); }}Install the following dependencies:
npm install @praxisjs/css @morphos/iconspnpm add @praxisjs/css @morphos/iconsyarn add @praxisjs/css @morphos/iconsbun add @praxisjs/css @morphos/iconsCopy and paste the following code into your project.
import { StatefulComponent } from "@praxisjs/core";import { cx, Stylesheet, Styled, tokenVars } from "@praxisjs/css";import { Component, Prop, State } from "@praxisjs/decorators";import type { Children } from "@praxisjs/shared";import { Icon } from "@morphos/icons";import { KosmesisTokens } from "@/lib/kosmesis-theme";const t = tokenVars(KosmesisTokens);class TaskStyles extends Stylesheet { $root = this.css({ borderRadius: "0.5rem", border: `1px solid ${t.border}`, backgroundColor: t.card, color: t.cardForeground, }); $trigger = this.css({ display: "flex", width: "100%", alignItems: "center", gap: "0.5rem", padding: "0.5rem 0.75rem", fontSize: "0.875rem", cursor: "pointer", }); $chevron = this.css({ display: "inline-block", flexShrink: 0, transition: "transform 150ms ease" }).on("&[data-open]", { transform: "rotate(90deg)", }); $dot = this.css({ display: "flex", height: "1rem", width: "1rem", flexShrink: 0, alignItems: "center", justifyContent: "center", borderRadius: "9999px", fontSize: "0.625rem", lineHeight: 1, backgroundColor: t.muted, }) .on('[data-status="done"] &', { backgroundColor: t.primary, color: t.primaryForeground }) .on('[data-status="running"] &', { backgroundColor: `color-mix(in oklab, ${t.primary} 30%, transparent)` }) .on('[data-status="error"] &', { backgroundColor: t.destructive, color: t.destructiveForeground }); $title = this.css({ flex: "1 1 0%", textAlign: "left", fontWeight: 500 }); $content = this.css({ display: "flex", flexDirection: "column", gap: "0.375rem", padding: "0 0.75rem 0.75rem 2.25rem", fontSize: "0.75rem", color: t.mutedForeground, }).on('&[data-state="closed"]', { display: "none" });}export type TaskStatus = "pending" | "running" | "done" | "error";export interface TaskProps { title: string; status?: TaskStatus; defaultOpen?: boolean; class?: string; children?: Children;}@Component()export class Task extends StatefulComponent { @Styled(TaskStyles) $s!: TaskStyles; @Prop() title = ""; @Prop() status: TaskStatus = "pending"; @Prop() defaultOpen = false; @Prop() class?: string; @Prop() children?: Children; @State() _open = false; onBeforeMount() { this._open = this.defaultOpen; } toggle(): void { this._open = !this._open; } render() { return ( <div data-slot="task" data-status={() => this.status} class={cx(this.$s.$root, this.class)}> <button type="button" class={this.$s.$trigger} onClick={() => { this.toggle(); }}> <span data-open={() => (this._open ? "" : undefined)} class={this.$s.$chevron}> <Icon name="ChevronRight" size={14} /> </span> <span class={this.$s.$dot}> {() => this.status === "done" ? ( <Icon name="Check" size={10} /> ) : this.status === "error" ? ( <Icon name="X" size={10} /> ) : ( "" ) } </span> <span class={this.$s.$title}>{this.title}</span> </button> <div data-state={() => (this._open ? "open" : "closed")} class={this.$s.$content}> {this.children} </div> </div> ); }}Examples
About
Purely presentational — no Morphos equivalent. Distinct from Steps (a flat non-collapsible
list of many steps) and ChainOfThought (reasoning text, not task status): this is one named task
whose status is expected to change live as it runs.
Usage
import { Task } from "@/components/ui/task";
<Task title="Refactor auth module" status="running" defaultOpen>
<span>src/auth/login.ts</span>
<span>src/auth/session.ts</span>
</Task>Props
| Prop | Type | Default |
|---|---|---|
title | string | — |
status | "pending" | "running" | "done" | "error" | "pending" |
defaultOpen | boolean | false |
class | string | — |