Velocity UI
Loading…
Menu

Code Block

A terminal-like code block with typing effects and copy animation.

Installation

Add this component to your project using the CLI:

terminal
npx vui-registry-cli-v1 add code-block

Source Code

code-block.tsx
'use client'

import React, { useState, useEffect } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { cn } from '@/lib/utils'
import { Check, Copy, Terminal } from 'lucide-react'

interface CodeBlockProps {
  code: string
  language?: string
  filename?: string
  showLineNumbers?: boolean
  className?: string
}

export function CodeBlock({
  code,
  language = 'typescript',
  filename,
  showLineNumbers = true,
  className,
}: CodeBlockProps) {
  const [copied, setCopied] = useState(false)
  const [displayedCode, setDisplayedCode] = useState('')

  useEffect(() => {
    let i = 0
    const interval = setInterval(() => {
      setDisplayedCode((prev) => {
        if (i < code.length) {
          i++
          return code.slice(0, i)
        }
        clearInterval(interval)
        return prev
      })
    }, 20) // Typewriter speed
    return () => clearInterval(interval)
  }, [code])

  const copyToClipboard = () => {
    navigator.clipboard.writeText(code)
    setCopied(true)
    setTimeout(() => setCopied(false), 2000)
  }

  return (
    <div
      className={cn(
        'relative rounded-xl border border-border bg-card overflow-hidden shadow-2xl',
        className
      )}
    >
      {/* Header */}
      <div className="flex items-center justify-between px-4 py-3 bg-foreground/5 border-b border-border/10">
        <div className="flex items-center gap-2">
          <div className="flex gap-1.5">
            <div className="w-3 h-3 rounded-full bg-red-500/80" />
            <div className="w-3 h-3 rounded-full bg-amber-400/80" />
            <div className="w-3 h-3 rounded-full bg-emerald-500/80" />
          </div>
          {filename && (
            <span className="ml-3 text-xs text-muted-foreground font-mono flex items-center gap-1.5">
              <Terminal size={12} />
              {filename}
            </span>
          )}
        </div>
        <button
          onClick={copyToClipboard}
          className="text-muted-foreground hover:text-white transition-colors"
          aria-label="Copy code"
        >
          <AnimatePresence mode="wait" initial={false}>
            {copied ? (
              <motion.span
                key="check"
                initial={{ scale: 0.5, opacity: 0 }}
                animate={{ scale: 1, opacity: 1 }}
                exit={{ scale: 0.5, opacity: 0 }}
              >
                <Check size={14} />
              </motion.span>
            ) : (
              <motion.span
                key="copy"
                initial={{ scale: 0.5, opacity: 0 }}
                animate={{ scale: 1, opacity: 1 }}
                exit={{ scale: 0.5, opacity: 0 }}
              >
                <Copy size={14} />
              </motion.span>
            )}
          </AnimatePresence>
        </button>
      </div>

      {/* Code Area */}
      <div className="relative p-4 overflow-x-auto bg-background">
        <pre className="font-mono text-sm leading-relaxed">
          <code className="block min-w-full">
            {displayedCode.split('
').map((line, i) => (
              <div key={i} className="table-row">
                {showLineNumbers && (
                  <span className="table-cell select-none text-muted-foreground/30 text-right pr-4 w-8">
                    {i + 1}
                  </span>
                )}
                <span className="table-cell text-foreground whitespace-pre">
                  {line || ' '}
                </span>
              </div>
            ))}
          </code>
        </pre>
        {/* Cursor Effect */}
        <motion.div
          animate={{ opacity: [1, 0] }}
          transition={{ repeat: Infinity, duration: 0.8 }}
          className="inline-block w-2 h-4 bg-primary ml-1 align-middle"
          style={{ display: displayedCode.length < code.length ? 'inline-block' : 'none' }}
        />
      </div>
    </div>
  )
}

// Example usage for Preview
export function CodeBlockPreview() {
  const sampleCode = `import { useState } from 'react'
import { motion } from 'framer-motion'

export function Counter() {
  const [count, setCount] = useState(0)
  
  return (
    <motion.button
      whileTap={{ scale: 0.95 }}
      onClick={() => setCount(c => c + 1)}
    >
      Count is {count}
    </motion.button>
  )
}`

  return (
    <div className="w-full max-w-2xl p-4">
      <CodeBlock code={sampleCode} filename="Counter.tsx" />
    </div>
  )
}

Dependencies

  • framer-motion: latest
  • lucide-react: latest
  • clsx: latest
  • tailwind-merge: latest

Props

Component property reference.

NameTypeDefaultDescription
codestring''Source code to display.
languagestring'typescript'Language hint for display.
filenamestringundefinedOptional filename badge.
showLineNumbersbooleantrueShow or hide line numbers.
classNamestringundefinedAdditional CSS classes.
Context Worth Keeping In Orbit

Most components here are inspired by outstanding libraries and creators in the ecosystem. I don’t claim to be the original author — this is my space for learning, rebuilding, and understanding great work at a deeper level.

I’m still a student of the craft, constantly studying the best and translating what I learn through my own perspective. Every piece reflects curiosity, respect for the community, and small creative touches that feel true to me.

I’ve done my best to credit inspirations properly. If anything is missing or inaccurate, I truly appreciate a message so it can be corrected with care.