Todos os componentes

Terminal UI

Terminal UI é um componente de dados para React e Tailwind CSS, da biblioteca UI Layouts, com licença MIT. Copia e cola no teu projeto.

UI Layouts MIT data

O que é

Terminal UI é um componente de dados para React e Tailwind CSS, da biblioteca UI Layouts, com licença MIT. Copia e cola no teu projeto.

Como usar

Abre o componente no catálogo, carrega em copiar, e cola no teu projeto. O prompt copiado leva o código e as regras para o teu agente o reproduzir sem alterar nada.

Abrir no catálogo

Cinco cópias grátis por mês. Sem cartão.

Não precisa de pacotes além do que o projeto já tem

Licença

Este componente vem de UI Layouts e é redistribuído sob a licença MIT, que permite uso comercial. O aviso de copyright viaja com o código.

Terminal UI

./registry/components/terminal-ui.tsx
'use client';
import { ShimmerLoader } from '@/components/ui/shimmer-loader';
import { TypeWritter } from '@/components/ui/typing-writter';
import { useCallback, useEffect, useRef, useState } from 'react';

type Phase =
  | { type: 'divider' }
  | {
      type: 'bar';
      labels: string[];
      icons: string[];
      duration: number;
      tokenTarget?: number; // omit -> no token counter
      showPercent?: boolean; // default true
    }
  | { type: 'lines'; lines: string[] }
  | { type: 'message'; text: string };

export interface IntroSequenceProps {
  onComplete?: () => void;
  className?: string;
  style?: React.CSSProperties;
}

interface RenderedItem {
  id: string;
  phase: Phase;
  lineIndex?: number;
}
// ── divider ──────────────────────────────────────────────────────────────────

function Divider() {
  return (
    <div
      style={{
        color: 'rgba(210,190,255,0.2)',
        fontSize: 13,
        letterSpacing: '1px',
        fontFamily: "'Cascadia Code', 'Fira Code', 'Consolas', monospace",
      }}
    >
      {'─'.repeat(44)}
    </div>
  );
}

// ── sequence runner ──────────────────────────────────────────────────────────

const SEQUENCE: Phase[] = [
  { type: 'divider' },
  {
    type: 'bar',
    labels: ['Scanning…', 'Analysing…', 'Detecting…', 'Profiling…'],
    icons: ['✦', '◆', '✶', '❋', '✸'],
    duration: 3000,
    tokenTarget: 0.6, // shows "0.6k" counter
    showPercent: true,
  },
  {
    type: 'lines',
    lines: ['New Role Detected', 'New Team Detected', 'New Challenges'],
  },
  { type: 'divider' },
  {
    type: 'bar',
    labels: ['Booting…', 'Loading…', 'Preparing…', 'Initializing…'],
    icons: ['✦', '◆', '✶', '❋', '✸'],
    duration: 3000,
    showPercent: false, // no percent, no tokens — clean spinner
  },
  { type: 'message', text: "Let's build something great 🚀" },
  { type: 'divider' },
  {
    type: 'bar',
    labels: ['Syncing…', 'Configuring…', 'Setting up…', 'Onboarding…'],
    icons: ['◈', '◉', '⬡', '⬢', '◍'],
    duration: 3000,
    tokenTarget: 0.8, // tokens but no percent
    showPercent: false,
  },
];

export function NewIntroSequence({ onComplete, className, style }: IntroSequenceProps) {
  const [items, setItems] = useState<RenderedItem[]>([]);
  const phaseRef = useRef(0);
  const lineRef = useRef(0);
  const completedRef = useRef(false);

  const advance = useCallback(() => {
    const phase = SEQUENCE[phaseRef.current];
    if (!phase) {
      if (!completedRef.current) {
        completedRef.current = true;
        onComplete?.();
      }
      return;
    }

    if (phase.type === 'divider') {
      setItems((prev) => [...prev, { id: `divider-${phaseRef.current}`, phase }]);
      phaseRef.current++;
      setTimeout(advance, 80);
      return;
    }

    if (phase.type === 'bar') {
      setItems((prev) => [...prev, { id: `bar-${phaseRef.current}`, phase }]);
      phaseRef.current++;
      return;
    }

    if (phase.type === 'lines') {
      lineRef.current = 0;
      phaseRef.current++;
      typeNextLine(phase.lines);
      return;
    }

    if (phase.type === 'message') {
      setItems((prev) => [...prev, { id: `msg-${phaseRef.current}`, phase }]);
      phaseRef.current++;
      setTimeout(advance, 400);
      return;
    }
  }, [onComplete]);

  function typeNextLine(lines: string[]) {
    const idx = lineRef.current;
    if (idx >= lines.length) {
      setTimeout(advance, 200);
      return;
    }
    setItems((prev) => [
      ...prev,
      {
        id: `line-${phaseRef.current - 1}-${idx}`,
        phase: { type: 'lines', lines },
        lineIndex: idx,
      },
    ]);
    lineRef.current++;
  }

  useEffect(() => {
    setTimeout(advance, 300);
  }, []);

  return (
    <>
      <style>{`@keyframes blink { 0%,100%{opacity:1} 50%{opacity:0} }`}</style>
      <div
        className={className}
        style={{
          background: '#0f0e17',
          padding: '24px 28px',
          display: 'flex',
          flexDirection: 'column',
          gap: 10,
          fontFamily: "'Cascadia Code', 'Fira Code', 'Consolas', monospace",
          minWidth: 520,
          ...style,
        }}
      >
        {items.map((item) => {
          const { id, phase } = item;

          if (phase.type === 'divider') return <Divider key={id} />;

          if (phase.type === 'bar') {
            return (
              <ShimmerLoader
                key={id}
                labels={phase.labels}
                icons={phase.icons}
                duration={phase.duration}
                tokenTarget={phase.tokenTarget}
                showPercent={phase.showPercent}
                onComplete={() => {
                  setTimeout(advance, 150);
                }}
              />
            );
          }

          if (phase.type === 'lines' && item.lineIndex !== undefined) {
            const isLast = item.lineIndex === phase.lines.length - 1;
            return (
              <TypeWritter
                key={id}
                text={phase.lines[item.lineIndex]}
                onComplete={() => {
                  setTimeout(() => typeNextLine(phase.lines), isLast ? 0 : 200);
                }}
              />
            );
          }

          if (phase.type === 'message') {
            return (
              <div
                key={id}
                style={{
                  color: 'rgb(160,130,255)',
                  fontWeight: 700,
                  fontSize: 14,
                  paddingLeft: 4,
                }}
              >
                {phase.text}
              </div>
            );
          }

          return null;
        })}
      </div>
    </>
  );
}

Componentes parecidos