Cómo construir una sección escaparate de SDK multilenguaje en React
Un escaparate de SDK multilenguaje en React guarda en el state el ID del lenguaje activo, muestra las pestañas de lenguaje como botones y permuta el panel de código con AnimatePresence mode='wait' de Framer Motion para que los fragmentos saliente y entrante no se solapen. Cada panel muestra el comando de instalación, un bloque de código con scroll y un botón de copia que se reinicia tras 2 segundos.
- Stack: React 18, Framer Motion 11, lucide-react, aproximadamente 385 líneas, cero bibliotecas de resaltado de sintaxis.
- El feedback de copia usa un setTimeout de 2 segundos que reinicia el state copiedField; el mismo handler cubre tanto el comando de instalación como el bloque de código.
- Transiciones de pestañas: AnimatePresence mode='wait' con entrada en desplazamiento y (y:16→0) y salida en desplazamiento y (y:0→-8), 350ms, ease personalizado [0.16,1,0.3,1].
- Accesible: el comando de instalación y el bloque de código tienen cada uno su propio botón de copia; no se usa ninguna acción solo con icono sin una etiqueta adyacente.
- Totalmente responsive mediante flexWrap en la fila de pestañas y la barra de instalación; el bloque de código se desplaza horizontalmente en viewports estrechos.
El SDK Showcase es una sección que permite a los visitantes alternar entre ejemplos de código por lenguaje, JavaScript, Python, Go, Ruby, en un único panel animado. Gestiona el comando de instalación, un bloque de código copiable, un badge de versión y un enlace directo a la documentación del lenguaje, todo sin recurrir a una biblioteca de resaltado de sintaxis. El resultado es un bloque ligero y adaptable al tema que encaja en cualquier landing page de producto para desarrolladores.
Anatomía
La sección se divide en tres zonas. Un encabezado centrado contiene un badge opcional, un título y un subtítulo. Debajo, una fila horizontal de botones de pestaña con forma de píldora muestra cada lenguaje con un punto de color, su nombre y una cadena de versión en monospace. La pestaña activa dispara una tarjeta animada que tiene a su vez tres subzonas: una barra de instalación arriba (icono de terminal, comando de instalación, botones Copiar y Docs), un bloque de código en el centro (pre monospace, botón de copia flotante) y un pie que repite el nombre del lenguaje más el badge de versión y renderiza un botón CTA Instalar.
Cómo funciona
El cambio de lenguaje se apoya en dos piezas de state: activeId (qué pestaña de lenguaje está seleccionada) y copiedField (qué botón de copia está en su estado 'copiado'). Cuando se hace clic en una pestaña, activeId se actualiza y AnimatePresence captura el panel saliente, lo anima hacia la salida (opacity 0, y -8) y luego monta el nuevo panel (opacity 0, y 16 → 1, y 0). El handler de copia llama a navigator.clipboard.writeText, asigna a copiedField una clave de cadena ('install' o 'code') y después programa un setTimeout para limpiarla. La constante de ease [0.16, 1, 0.3, 1] es un cubic-bezier personalizado que da a las transiciones una sensación de salida-rápida-entrada-lenta sin necesitar un preset con nombre.
Cómo crearlo en React
Definir la estructura de datos de los lenguajes
Crea una interfaz SdkLanguage con id, name, version, installCmd, code, un docsUrl opcional y una cadena color para el punto. Pásala como prop con valores por defecto razonables para que el componente funcione de inmediato. Mantén los fragmentos de código reales como template literals en el array de datos por defecto.
interface SdkLanguage { id: string; name: string; version: string; installCmd: string; code: string; docsUrl?: string; color: string; }Conectar el state de las pestañas y la lógica de copia
Inicializa activeId con el id del primer lenguaje y copiedField a null. El callback handleCopy escribe en el portapapeles, asigna a copiedField una clave de cadena y la limpia tras 2000ms. Envuélvelo en useCallback para que no se reconstruya en cada render.
const [activeId, setActiveId] = useState(languages[0]?.id ?? ""); const [copiedField, setCopiedField] = useState<string | null>(null); const handleCopy = useCallback((text: string, field: string) => { navigator.clipboard.writeText(text).catch(() => {}); setCopiedField(field); setTimeout(() => setCopiedField(null), 2000); }, []);Animar el cambio de panel con AnimatePresence
Envuelve el panel activo en AnimatePresence con mode='wait'. Usa el id del lenguaje como key para que React desmonte el panel antiguo antes de montar el nuevo. Define initial a opacity:0, y:16, animate a opacity:1, y:0 y exit a opacity:0, y:-8 para un deslizamiento y fundido limpios.
<AnimatePresence mode="wait"> {active && ( <motion.div key={active.id} initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -8 }} transition={{ duration: 0.35, ease: [0.16, 1, 0.3, 1] }} > {/* install bar + code block + footer */} </motion.div> )} </AnimatePresence>Construir la barra de instalación y el bloque de código
La barra de instalación es una fila flex: un icono Terminal más el installCmd en un elemento code monospace a la izquierda, y los botones Copiar y enlace Docs opcional a la derecha. El bloque de código es una etiqueta pre dentro de un div con padding con overflowX:auto y un botón de copia flotante en la esquina superior derecha. Usa propiedades CSS personalizadas para todos los colores de modo que se adapte a cualquier preset de tema.
Cuándo usarlo
Usa esta sección en cualquier landing page de producto para desarrolladores donde distribuyes SDKs para varios lenguajes y necesitas demostrarlo visualmente. Funciona bien en un bloque 'Empieza en 2 minutos', justo antes de una sección de precios o un CTA. Evítala cuando tu producto solo soporta un lenguaje; un bloque de código simple con un botón de copia es más limpio. En móvil la fila de pestañas se ajusta y el bloque de código se desplaza horizontalmente, lo cual está bien para leer pero no es ideal para el onboarding, considera una vista móvil simplificada si las conversiones importan ahí.
Usado por
- Stripe, La referencia de API de Stripe usa pestañas de lenguaje (curl, Ruby, Python, PHP, Node, Go, .NET, Java) para mostrar la misma petición en cada SDK, con copia en un clic en cada fragmento.
- Twilio, La documentación de Twilio incluye un selector de lenguaje persistente en la parte superior de los ejemplos de código, conservando el lenguaje elegido entre la navegación de páginas para que los desarrolladores se queden en su stack.
- Algolia, Las páginas de documentación de Algolia muestran fragmentos de instalación multilenguaje con pestañas para JavaScript, PHP, Python, Ruby, Go y otros, cada uno con un botón de copia y un badge de versión.
- Resend, La sección de inicio de Resend muestra los comandos de instalación de SDK para Node, Python, Ruby, PHP, Go y Rust en un bloque de código con pestañas, el mismo patrón, aplicado al onboarding de una API de email.
FAQ
¿Por qué usar AnimatePresence mode='wait' en lugar de un renderizado condicional simple?
mode='wait' garantiza que la animación de salida del panel saliente se complete antes de que se monte el panel entrante. Sin él, ambos paneles están brevemente en el DOM a la vez y la transición parece un glitch de superposición en lugar de un intercambio deliberado.
¿Puedo añadir resaltado de sintaxis sin romper la animación?
Sí. Reemplaza el contenido de la etiqueta pre con la salida de una biblioteca como shiki o prism-react-renderer. El wrapper AnimatePresence rodea todo el panel, no el pre, así que la animación no se ve afectada. Solo asegúrate de que la salida resaltada sea estable entre renders para evitar parpadeos.
¿Qué ocurre si navigator.clipboard no está disponible?
La función handleCopy llama a .catch(() => {}) para tragar silenciosamente el error, de modo que la interfaz no se rompe. Si necesitas un fallback visible, reemplaza el catch por una actualización de state que muestre un tooltip 'Copia fallida'.
¿Cómo conservo el lenguaje seleccionado entre las navegaciones de página?
Eleva el state activeId fuera del componente y sincronízalo con localStorage o un parámetro de query de la URL. Al montar, lee el valor almacenado e inicializa useState con él en lugar de recurrir por defecto a languages[0].id.