Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion app/playground/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TerminalApp } from '@/components/terminal-app'
import { Terminal, TerminalCommand, TerminalDiff, TerminalOutput, TerminalSpinner, TerminalBadge, ThemeSwitcher } from '@/components/terminal'
import { Terminal, TerminalCommand, TerminalDiff, TerminalOutput, TerminalSpinner, TerminalBadge, ThemeSwitcher, TerminalProgressGroup } from '@/components/terminal'
import { TerminalProgress } from '@/components/terminal-progress'
import { LogDemo } from './log-demo'
import { PromptDemo } from './prompt-demo'
Expand Down Expand Up @@ -52,6 +52,40 @@ export default function PlaygroundPage() {
</Terminal>
</section>

<section className="flex flex-col gap-2">
<h2 className="text-lg font-semibold font-mono text-[var(--term-fg)]">
TerminalProgressGroup
</h2>
<p className="text-sm text-[var(--term-fg-dim)] font-mono">
Display multiple related progress bars for multi-step tasks.
</p>
<Terminal title="build-pipeline.sh">
<TerminalCommand>run-build</TerminalCommand>
<TerminalProgressGroup
title="Build Pipeline"
items={[
{ label: 'Compile TypeScript', percent: 100, status: 'complete' },
{ label: 'Bundle assets', percent: 75, status: 'active', variant: 'blue' },
{ label: 'Optimize images', percent: 45, status: 'active', variant: 'yellow' },
{ label: 'Deploy to production', percent: 0, status: 'pending' },
]}
/>
</Terminal>
<Terminal title="collapsible-demo.sh">
<TerminalCommand>deploy --verbose</TerminalCommand>
<TerminalProgressGroup
title="Deployment Steps"
collapsible
items={[
{ label: 'Build Docker image', percent: 100, status: 'complete' },
{ label: 'Push to registry', percent: 100, status: 'complete' },
{ label: 'Update Kubernetes', percent: 100, status: 'complete' },
{ label: 'Health checks', percent: 100, status: 'complete' },
]}
/>
</Terminal>
</section>

<section className="flex flex-col gap-2">
<h2 className="text-lg font-semibold font-mono text-[var(--term-fg)]">
TerminalSpinner
Expand Down
150 changes: 150 additions & 0 deletions components/terminal-progress-group.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
'use client'

import { useState } from 'react'
import { TerminalProgress } from './terminal-progress'

export interface ProgressItem {
/** Label for this step */
label: string
/** Progress percentage (0-100) */
percent: number
/** Variant color */
variant?: 'green' | 'blue' | 'yellow' | 'red' | 'purple' | 'cyan'
/** Step status (determines icon) */
status?: 'pending' | 'active' | 'complete' | 'error'
}

export interface TerminalProgressGroupProps {
/** Array of progress items */
items: ProgressItem[]
/** Show overall progress (default: true) */
showTotal?: boolean
/** Collapsible (default: false) */
collapsible?: boolean
/** Title for the progress group */
title?: string
/** Additional CSS classes */
className?: string
}

const statusIcons: Record<NonNullable<ProgressItem['status']>, string> = {
pending: '○',
active: '⟳',
complete: '✓',
error: '✗',
}

const statusColors: Record<NonNullable<ProgressItem['status']>, string> = {
pending: 'var(--term-fg-dim)',
active: 'var(--term-blue)',
complete: 'var(--term-green)',
error: 'var(--term-red)',
}

/**
* Display multiple related progress bars for multi-step tasks.
* Calculates and shows overall progress with optional collapsible details.
*
* @param items - Array of progress items with labels, percentages, and status
* @param showTotal - Display overall progress bar at top (default: true)
* @param collapsible - Allow expanding/collapsing details (default: false)
* @param title - Optional title for the progress group
* @param className - Additional classes applied to the root element
*
* @example
* ```tsx
* <TerminalProgressGroup
* title="Build Pipeline"
* items={[
* { label: 'Compile TypeScript', percent: 100, status: 'complete' },
* { label: 'Bundle assets', percent: 80, status: 'active', variant: 'blue' },
* { label: 'Deploy to prod', percent: 0, status: 'pending' },
* ]}
* />
* ```
*/
export function TerminalProgressGroup({
items,
showTotal = true,
collapsible = false,
title,
className = '',
}: TerminalProgressGroupProps) {
const [expanded, setExpanded] = useState(!collapsible)

// Calculate overall progress (average)
const totalPercent = items.length > 0
? Math.round(items.reduce((sum, item) => sum + item.percent, 0) / items.length)
: 0

// Determine overall variant based on status
const hasError = items.some((item) => item.status === 'error')
const allComplete = items.every((item) => item.status === 'complete' || item.percent === 100)
const overallVariant = hasError ? 'red' : allComplete ? 'green' : 'blue'

return (
<div className={`flex flex-col gap-2 ${className}`.trim()}>
{/* Title */}
{title && (
<div className="text-[var(--term-fg)] font-mono text-sm font-semibold">
{title}
</div>
)}

{/* Overall progress */}
{showTotal && (
<div className="flex items-center gap-2">
{collapsible && (
<button
type="button"
onClick={() => setExpanded(!expanded)}
className="text-[var(--term-fg-dim)] hover:text-[var(--term-fg)] transition-colors"
aria-label={expanded ? 'Collapse details' : 'Expand details'}
>
{expanded ? '▼' : '▶'}
</button>
)}
<div className="flex-1">
<TerminalProgress
label="Overall Progress"
percent={totalPercent}
variant={overallVariant}
width={30}
/>
</div>
</div>
)}

{/* Individual progress items */}
{expanded && items.length > 0 && (
<div className="flex flex-col gap-1 ml-4 border-l-2 border-[var(--glass-border)] pl-3">
{items.map((item, i) => {
const status = item.status || (item.percent === 100 ? 'complete' : item.percent > 0 ? 'active' : 'pending')
const icon = statusIcons[status]
const iconColor = statusColors[status]
const variant = item.variant || (status === 'error' ? 'red' : status === 'complete' ? 'green' : 'blue')

return (
<div key={i} className="flex items-center gap-2">
<span
className="flex-shrink-0 w-4 text-center font-mono"
style={{ color: iconColor }}
>
{icon}
</span>
<div className="flex-1">
<TerminalProgress
label={item.label}
percent={item.percent}
variant={variant}
width={20}
/>
</div>
</div>
)
})}
</div>
)}
</div>
)
}
1 change: 1 addition & 0 deletions components/terminal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ export function TerminalSpinner({ text }: TerminalSpinnerProps) {
}

export { TerminalProgress } from './terminal-progress'
export { TerminalProgressGroup, type ProgressItem } from './terminal-progress-group'
export { TerminalPrompt } from './terminal-prompt'
export { TerminalTree } from './terminal-tree'
export type { TreeNode, TreeRenderContext, TerminalTreeProps } from './terminal-tree'
Expand Down