Componenteditors1.35 KB gzipZero dependencies

Queued Jobs

Job queue panel with status, progress bars and cancel action.

Preview

Live Preview
Ticket confirmation emailRunning

Installation

Tokens guide →

Add to your project via CLI (zero dependencies, code you own):

npx bigbullui add queued-jobs

Or install the full npm package:

npm install bigbullui
import { QueuedJobs } from "@/components/ui/queued-jobs";

<QueuedJobs />

Usage guidance

Use Queued Jobs when a ticket stub surface needs Job queue panel with status, progress bars and cancel action. Keep props minimal and prefer composition with sibling stubs.

Avoid Queued Jobs for long form flows or dense data grids where a dedicated layout block fits better.

Source code

queued-jobs.tsx

Zero dependencies (only React + utils). Copy and paste directly into your project:

"use client";

import * as React from "react";
import { cn } from "./lib/utils";

export type QueueJobStatus = "queued" | "running" | "done" | "failed";

export interface QueueJob {
  id: string;
  label: string;
  status: QueueJobStatus;
  progress?: number;
}

export interface QueuedJobsProps extends React.HTMLAttributes<HTMLDivElement> {
  jobs: QueueJob[];
  onCancel?: (id: string) => void;
}

/** Background queued jobs: status + progress bar + cancel action. */
export function QueuedJobs({ jobs, onCancel, className, ...props }: QueuedJobsProps) {
  const [list, setList] = React.useState(jobs);

  const statusMeta: Record<QueueJobStatus, { label: string; tone: string }> = {
    queued: { label: "Queued", tone: "text-muted-foreground" },
    running: { label: "Running", tone: "text-accent" },
    done: { label: "Done", tone: "text-success" },
    failed: { label: "Failed", tone: "text-destructive" },
  };

  return (
    <div className={cn("w-full max-w-md space-y-1.5", className)} {...props}>
      <style>{`@keyframes qjIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }`}</style>
      {list.map((job, idx) => {
        const m = statusMeta[job.status];
        return (
          <div
            key={job.id}
            className="rounded-md border border-border bg-card px-3 py-2.5 animate-[qjIn_0.3s_ease-out_both] motion-reduce:animate-none"
            style={{ animationDelay: `${idx * 55}ms` }}
          >
            <div className="flex items-center gap-2">
              <span className={cn("size-1.5 shrink-0 rounded-full", job.status === "running" ? "bg-accent animate-pulse motion-reduce:animate-none" : job.status === "done" ? "bg-success" : job.status === "failed" ? "bg-destructive" : "bg-border")} aria-hidden="true" />
              <span className="min-w-0 flex-1 truncate text-sm">{job.label}</span>
              <span className={cn("shrink-0 font-mono text-[10px] uppercase tracking-wider", m.tone)}>{m.label}</span>
              {(job.status === "queued" || job.status === "running") && (
                <button
                  onClick={() => { setList((l) => l.filter((j) => j.id !== job.id)); onCancel?.(job.id); }}
                  className="shrink-0 rounded-sm p-0.5 text-muted-foreground transition-colors hover:text-destructive focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring motion-reduce:transition-none"
                  aria-label={`Cancel ${job.label}`}
                >
                  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><path d="M18 6L6 18M6 6l12 12" /></svg>
                </button>
              )}
            </div>
            {job.status === "running" && job.progress !== undefined && (
              <div className="mt-1.5 h-1 overflow-hidden rounded-full bg-border/50">
                <div className="h-full rounded-full bg-accent transition-all duration-500 motion-reduce:transition-none" style={{ width: `${job.progress}%` }} />
              </div>
            )}
          </div>
        );
      })}
      {list.length === 0 && <p className="rounded-md border border-dashed border-border p-4 text-center text-sm text-muted-foreground">Queue is empty.</p>}
    </div>
  );
}

Props

PropTypeDescription
jobsQueueJob[]
onCancel(id: string) => void