Code Block

Bloques de código pensados para respuestas de IA. Resaltado de sintaxis que sigue el ritmo del streaming, copiar, descargar y ajustar líneas, números de línea y líneas resaltadas, diffs con aceptar y rechazar, y una terminal para comandos.

El código de una respuesta de IA llega unos pocos caracteres cada vez, a menudo como un diff que alguien tiene que aprobar. Code Block resalta mientras llega, sigue las líneas nuevas salvo que el lector se haya desplazado hacia arriba, y mantiene copiar, descargar y ajustar fuera del camino hasta que el código está completo.

El resaltado usa Shiki con los temas claro y oscuro de GitHub, cargados por lenguaje bajo demanda. Solo se tokenizan las líneas nuevas a medida que llega el texto, así que los archivos largos siguen siendo rápidos, y ambos temas se renderizan a la vez para que cambiar el esquema de color nunca parpadee. Los bloques de más de 16 líneas se pliegan tras «Show all».

Pasa un diff unificado y obtienes números de línea antiguos y nuevos, un recuento de cambios y filas teñidas; al copiar se obtiene la versión nueva, no el diff. Añade una revisión y el lector puede aceptar o rechazar el cambio, con ⌘↵ y ⌘⌫ mientras el bloque tiene el foco. CodeFence conecta el mismo bloque a Streamdown, y CodeTerminal muestra comandos con su salida y código de salida.

  1. Añade el registro Pro a components.json

    components.json
    {
      "registries": {
        "@hextaui-pro": {
          "url": "https://hextaui.com/r/pro/{name}.json",
          "headers": {
            "Authorization": "Bearer ${HEXTAUI_PRO_TOKEN}"
          }
        }
      }
    }
  2. Añade tu token

    Crea un token en tu página de cuenta y colócalo en .env.local como HEXTAUI_PRO_TOKEN.

  3. Añade el bloque

    pnpm dlx shadcn@latest add @hextaui-pro/code-block

En Markdown con Streamdown

Registra CodeFence como renderizador. Lee el lenguaje y los ajustes del bloque como title="app/page.tsx", {2,4-6} y showLineNumbers, y mantiene el resaltado mientras el bloque aún está en streaming.

"use client"

import { Streamdown } from "streamdown"

import { CodeFence, codeFenceLanguages } from "@/components/blocks/code-block/code-fence"

export function Answer({
  markdown,
  streaming,
}: {
  markdown: string
  streaming: boolean
}) {
  return (
    <Streamdown
      mode="streaming"
      isAnimating={streaming}
      plugins={{
        renderers: [{ language: codeFenceLanguages, component: CodeFence }],
      }}
    >
      {markdown}
    </Streamdown>
  )
}

Revisar un cambio

Muestra una edición propuesta como diff y deja que el lector la acepte o la rechace, con ⌘↵ y ⌘⌫ mientras el bloque tiene el foco.

"use client"

import * as React from "react"

import { CodeBlock, type ReviewStatus } from "@/components/blocks/code-block/code-block"

export function ProposedEdit({
  patch,
  onAccept,
  onReject,
}: {
  patch: string
  onAccept: () => Promise<void>
  onReject: () => void
}) {
  const [status, setStatus] = React.useState<ReviewStatus>("pending")

  return (
    <CodeBlock
      code={patch}
      language="tsx"
      filename="components/search.tsx"
      diff
      review={{
        status,
        onAccept: async () => {
          await onAccept()
          setStatus("accepted")
        },
        onReject: () => {
          onReject()
          setStatus("rejected")
        },
      }}
    />
  )
}

Por sí solo

Pasa el código y el lenguaje directamente, por ejemplo cuando una llamada a herramienta devuelve el contenido de un archivo.

import { CodeBlock } from "@/components/blocks/code-block/code-block"

export function FileContents({ path, contents }: { path: string; contents: string }) {
  return (
    <CodeBlock
      code={contents}
      language={path.split(".").pop()}
      filename={path}
      lineNumbers
      highlight={[3, 4]}
    />
  )
}

Anatomía

Las partes que compones, de fuera hacia dentro.

ParteDescripción
CodeBlockEl bloque en sí: encabezado, acciones, código y la barra de revisión opcional.
CodeFenceUn renderizador de Streamdown que convierte el código delimitado en un CodeBlock.
CodeTerminalUn comando con su salida en streaming y su estado de salida.
CopyButton, DownloadButton, WrapToggleLas acciones del encabezado, exportadas para tus propios encabezados.
LanguageIconLa marca de lenguaje usada en el encabezado.
PropTipoPredeterminado
codeEl código fuente, o un diff unificado cuando diff está definido.
string–
languageId o alias de lenguaje de Shiki, como tsx, py o bash. Los ids desconocidos se renderizan como texto plano.
string–
filenameSe muestra en el encabezado y se usa para las descargas.
string–
streamingMantiene el resaltado incremental, sigue las líneas nuevas y deshabilita las acciones.
booleanfalse
diffTrata el código como un diff unificado.
booleanfalse
lineNumbersMuestra números de línea. Los diffs los muestran salvo que se establezca en false.
boolean–
startLineNúmero de la primera línea.
number1
highlightNúmeros de línea que marcar.
number[][]
defaultWrapEmpieza con las líneas largas ajustadas.
booleanfalse
collapseAfterSe colapsa cuando supera este número de líneas. 0 nunca colapsa.
number16
actionsControles adicionales en el encabezado, antes de los integrados.
ReactNode–
onApplyMuestra un botón Apply que confirma con «Applied».
() => unknown–
reviewMuestra la barra de revisión mientras está pendiente, y una insignia una vez aceptado o rechazado.
CodeBlockReview–
PropTipoPredeterminado
statusLa decisión actual.
"pending" | "accepted" | "rejected"–
onAcceptSe llama desde Accept o ⌘↵.
() => void–
onRejectSe llama desde Reject o ⌘⌫.
() => void–

CodeFence

Regístralo con Streamdown: plugins={{ renderers: [{ language: codeFenceLanguages, component: CodeFence }] }}.

PropTipoPredeterminado
codeContenido del bloque delimitado, desde Streamdown.
string–
languageLenguaje del bloque delimitado, desde Streamdown.
string–
metaTodo lo que sigue al lenguaje: title="…", {1,3-5}, showLineNumbers, startLine=10.
string–
isIncompleteTrue mientras el bloque delimitado sigue abierto.
boolean–
PropTipoPredeterminado
commandEl comando que se ejecutó.
string–
outputSalida hasta ahora. Añade a medida que llega.
string""
runningMuestra un spinner y sigue la salida nueva.
booleanfalse
exitCodeSe muestra al terminar. Cualquier valor distinto de 0 se marca como fallido.
number–
titleEtiqueta del encabezado.
string"Terminal"
KeyAcción
TabRecorre las acciones del encabezado, el área de código y Show all.
⌘↵Acepta una revisión pendiente mientras el foco está dentro del bloque.
⌘⌫Rechaza una revisión pendiente mientras el foco está dentro del bloque.
←→Desplaza las líneas largas cuando el área de código tiene el foco.
  • El área de código es una región enfocable con el nombre del archivo, por ejemplo «app/page.tsx code», para que los usuarios de teclado puedan desplazarla.
  • Las líneas modificadas no dependen del color. Los marcadores + y − son solo visuales, y los lectores de pantalla oyen «added» o «removed» antes de cada línea modificada.
  • Copy anuncia «Copied» y cada botón de icono tiene una etiqueta y un tooltip.
  • Los atajos de revisión se ignoran mientras se escribe en un campo, así que nunca roban pulsaciones.

Construido con

Los componentes gratuitos de HextaUI con los que está hecho Code Block. Cada uno se instala por separado.

Código

9 archivos, añadidos a components/blocks/code-block.