# Multi Progress

Parallel progress display for package managers, build pipelines, and deploy tools

## Installation

  <TabsTrigger value="cli">Command</TabsTrigger>
  <TabsTrigger value="manual">Manual</TabsTrigger>

```bash
npx shadcn@latest add @termcn/multi-progress
```

<Step>Copy and paste the following code into your project.</Step>

```tsx
import { Box, Text } from "ink";

import { useTheme } from "@/components/ui/theme-provider";

export type MultiProgressStatus = "pending" | "running" | "done" | "error";

export interface MultiProgressItem {
  id: string;
  label: string;
  value: number;
  total: number;
  status?: MultiProgressStatus;
  statusText?: string;
}

export interface MultiProgressProps {
  items: MultiProgressItem[];
  barWidth?: number;
  labelWidth?: number;
  compact?: boolean;
  showPercent?: boolean;
}

const truncate = (s: string, n: number): string =>
  s.length > n ? `${s.slice(0, n - 1)}…` : s.padEnd(n);

export const MultiProgress = ({
  items,
  barWidth = 20,
  labelWidth = 20,
  compact = false,
  showPercent = true,
}: MultiProgressProps) => {
  const theme = useTheme();

  const statusColor = (status: MultiProgressStatus | undefined): string => {
    switch (status) {
      case "done": {
        return theme.colors.success;
      }
      case "error": {
        return theme.colors.error;
      }
      case "pending": {
        return theme.colors.mutedForeground;
      }
      default: {
        return theme.colors.primary;
      }
    }
  };

  const renderBar = (item: MultiProgressItem): string => {
    const pct = item.total > 0 ? Math.min(1, item.value / item.total) : 0;
    const filled = Math.round(pct * barWidth);
    const empty = barWidth - filled;
    return `${"█".repeat(filled)}${"░".repeat(empty)}`;
  };

  return (
    <Box flexDirection="column">
      {items.map((item) => {
        const pct =
          item.total > 0
            ? Math.min(100, Math.round((item.value / item.total) * 100))
            : 0;
        const color = statusColor(item.status);
        const bar = renderBar(item);
        const label = truncate(item.label, labelWidth);

        if (compact) {
          return (
            <Box key={item.id} flexDirection="row" gap={1}>
              <Text color={color}>{label}</Text>
              <Text color={theme.colors.mutedForeground}>[</Text>
              <Text color={color}>{bar}</Text>
              <Text color={theme.colors.mutedForeground}>]</Text>
              {showPercent && (
                <Text color={theme.colors.mutedForeground}>
                  {String(pct).padStart(3)}%
                </Text>
              )}
              {item.statusText && (
                <Text color={theme.colors.mutedForeground} dimColor>
                  {item.statusText}
                </Text>
              )}
            </Box>
          );
        }

        return (
          <Box key={item.id} flexDirection="column" marginBottom={0}>
            <Box flexDirection="row" gap={1}>
              <Text bold color={color}>
                {label}
              </Text>
              {item.statusText && (
                <Text color={theme.colors.mutedForeground} dimColor>
                  {item.statusText}
                </Text>
              )}
            </Box>
            <Box flexDirection="row" gap={1}>
              <Text color={theme.colors.mutedForeground}>[</Text>
              <Text color={color}>{bar}</Text>
              <Text color={theme.colors.mutedForeground}>]</Text>
              {showPercent && (
                <Text color={theme.colors.mutedForeground}>
                  {String(pct).padStart(3)}%
                </Text>
              )}
            </Box>
          </Box>
        );
      })}
    </Box>
  );
};
```

<Step>Update the import paths to match your project setup.</Step>

## Usage

```tsx
import { MultiProgress } from "@/components/ui/multi-progress";
```

```tsx
<MultiProgress
  items={[
    {
      id: "api",
      label: "API Server",
      value: 80,
      total: 100,
      status: "running",
    },
    { id: "db", label: "Database", value: 100, total: 100, status: "done" },
    { id: "cache", label: "Redis", value: 30, total: 100, status: "running" },
  ]}
/>
```

## Examples

### Compact Mode

```tsx
import { MultiProgress } from "@/registry/bases/ink/ui/multi-progress";

export default function MultiProgressCompact() {
  return (
    <MultiProgress
      compact
      items={[
        { id: "1", label: "eslint", status: "running", total: 100, value: 50 },
        { id: "2", label: "tsc", status: "done", total: 100, value: 100 },
      ]}
    />
  );
}
```

## API Reference

### MultiProgress

| Prop          | Type                  | Default    |
| ------------- | --------------------- | ---------- |
| `items`       | `MultiProgressItem[]` | `required` |
| `barWidth`    | `number`              | `20`       |
| `labelWidth`  | `number`              | `20`       |
| `compact`     | `boolean`             | `false`    |
| `showPercent` | `boolean`             | `true`     |

### MultiProgressItem

| Prop         | Type                                          | Default    |
| ------------ | --------------------------------------------- | ---------- |
| `id`         | `string`                                      | `required` |
| `label`      | `string`                                      | `required` |
| `value`      | `number`                                      | `required` |
| `total`      | `number`                                      | `required` |
| `status`     | `"pending" \| "running" \| "done" \| "error"` | -          |
| `statusText` | `string`                                      | -          |