import { type ReactNode, useEffect, useState } from 'react';
import { RefreshCw, TerminalSquare } from 'lucide-react';
import { motion } from 'motion/react';
import { TextMorph } from 'torph/react';
import spinners from 'unicode-animations';
import type { BrailleSpinnerName } from 'unicode-animations';
import type { InstalledSkillsScopeState, UpdateSkillsResponse } from '../../features/skills/state';
import type { SkillScope } from '../../features/skills/types';
import { Badge } from '../components/ui/badge';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../components/ui/card';
import { cn } from '../lib/utils';
import type { InstallOutcome, RemoveOutcome } from './types';
import { scopeLabel } from './utils';
export function SummaryCard(props: {
title: string;
subtitle: string;
icon: ReactNode;
count: number;
}) {
return (
{props.icon}
{props.title}
{props.count}
{props.subtitle}
);
}
export function StatusBanner(props: { icon: ReactNode; className: string; message: string }) {
return (
{props.icon}
{props.message}
);
}
export function RemoveOperationCard(props: { outcome: RemoveOutcome }) {
const { command, names, scope, status } = props.outcome;
return (
Remove Command Output
Removed {names.length} skill{names.length === 1 ? '' : 's'} from {scopeLabel(scope)}{' '}
scope
{command.command.join(' ')}
{status === 'success' ? 'Succeeded' : 'Failed'}
{command.stdout.trim() ? (
) : (
No stdout output.
)}
{command.stderr.trim() ? : null}
);
}
export function InstallOperationCard(props: { outcome: InstallOutcome }) {
const { command, source, scope, status } = props.outcome;
return (
Install Command Output
Installed {source} in {scopeLabel(scope)} scope
{command.command.join(' ')}
{status === 'success' ? 'Succeeded' : 'Failed'}
{command.stdout.trim() ? (
) : (
No stdout output.
)}
{command.stderr.trim() ? : null}
);
}
export function UpdateOperationCard(props: { results: UpdateSkillsResponse[] }) {
return (
Update Command Output
Latest `npx skills update` output
{props.results.map((result, index) => (
{scopeLabel(result.scope)}
{result.command.command.join(' ')}
{result.command.ok ? 'Succeeded' : 'Failed'}
{result.command.stdout.trim() ? (
) : (
No stdout output.
)}
{result.command.stderr.trim() ? (
) : null}
))}
);
}
export function CommandOutputCard(props: {
scope: SkillScope;
scopeState: InstalledSkillsScopeState;
}) {
const { scope, scopeState } = props;
const command = scopeState.command;
return (
{scopeLabel(scope)} Command Output
Latest `npx skills` output for this scope
{command ? (
<>
{command.command.join(' ')}
{command.ok ? 'Succeeded' : 'Failed'}
{command.stdout.trim() ? (
) : (
No stdout output.
)}
{command.stderr.trim() ? : null}
{scopeState.error ? (
{scopeState.error}
) : null}
{scopeState.stale ? (
Showing stale data from previous successful load.
) : null}
>
) : (
No command output available yet.
)}
);
}
export function OutputBlock(props: { label: string; value: string }) {
return (
{props.label}
{props.value.trim()}
);
}
export function AnimatedText(props: { children: string; className?: string; as?: 'span' | 'p' }) {
return (
{props.children}
);
}
export function Spinner(props: {
label?: string;
className?: string;
name?: BrailleSpinnerName;
size?: number;
}) {
const name = props.name ?? 'braille';
const [frame, setFrame] = useState(0);
useEffect(() => {
const s = spinners[name];
const timer = setInterval(() => setFrame((f) => (f + 1) % s.frames.length), s.interval);
return () => clearInterval(timer);
}, [name]);
return (
{spinners[name].frames[frame]}
);
}
export function LoadingIndicator(props: { label: string; className?: string }) {
return (
);
}
export function PageLoadingState() {
return (
);
}
export function MetadataRow(props: { label: string; value: string; mono?: boolean }) {
return (
{props.label}
{props.value}
);
}