Tutorial WordPress: Crear un layout tipo documentación de plugin con índice sticky

·

·

Introducción

Este tutorial explica paso a paso cómo crear un layout de documentación para un plugin de WordPress con un índice sticky (fijo) que permite navegación rápida por secciones, resaltado del elemento activo, scroll suave y un diseño responsivo y accesible. Incluye el esqueleto del plugin, la plantilla HTML generada por un shortcode, el CSS para el layout y el comportamiento sticky, y el JavaScript para scroll suave y scrollspy usando Intersection Observer. Además se tratan consideraciones de seguridad, rendimiento y mejoras posibles.

Estructura general y flujo

1) Estructura del plugin

Crea una carpeta en wp-content/plugins, por ejemplo doc-layout-sticky. Dentro, crea un archivo principal PHP y carpetas para css y js.

  1. doc-layout-sticky/
    • doc-layout-sticky.php
    • assets/
      • css/doc-layout.css
      • js/doc-layout.js

Archivo principal (encolado de assets y registro del shortcode)

Ejemplo mínimo del archivo principal del plugin:

lt?php
/
Plugin Name: Doc Layout Sticky
Description: Layout tipo documentación con índice sticky para plugins o páginas.
Version: 1.0
Author: Tu Nombre
Text Domain: doc-layout-sticky
/

if ( ! defined( ABSPATH ) ) {
    exit
}

/ Encolar estilos y scripts /
function dls_enqueue_assets() {
    wp_enqueue_style( dls-style, plugin_dir_url( __FILE__ ) . assets/css/doc-layout.css, array(), 1.0 )
    wp_enqueue_script( dls-script, plugin_dir_url( __FILE__ ) . assets/js/doc-layout.js, array(), 1.0, true )
    wp_localize_script( dls-script, dlsConfig, array(
        scrollOffset => 80, // ajuste si hay un header fijo
    ) )
}
add_action( wp_enqueue_scripts, dls_enqueue_assets )

/ Shortcode que genera el layout /
function dls_docs_shortcode( atts, content = null ) {
    // atributos por si quieres personalizar
    atts = shortcode_atts( array(
        title => ,
    ), atts, dls_docs )

    // Aquí podrías generar el índice dinámicamente el ejemplo usa contenido estático
    ob_start()
    ?>
    
lt!-- EJEMPLO: sustituir por contenido real o generar dinámicamente --gt
lth2gtIntroducciónlt/h2gt ltpgtContenido de la introducción...lt/pgt
lth2gtInstalaciónlt/h2gt ltpgtContenido de instalación...lt/pgt
lth2gtUsolt/h2gt ltpgtContenido de uso...lt/pgt
lth2gtAPIlt/h2gt ltpgtContenido de API...lt/pgt
lth2gtFAQlt/h2gt ltpgtPreguntas frecuentes...lt/pgt

2) HTML y estructura semántica recomendada

El layout elemental consta de un contenedor con dos zonas: el índice (nav) y el contenido (main). Usa roles y atributos ARIA cuando haga falta para mejorar la accesibilidad.

Ejemplo de estructura generada por el shortcode (ya vista en el PHP): un nav con una lista de enlaces a #anchors y un main con secciones con id únicos.

3) CSS: layout, sticky y responsive

Consejos clave:

Ejemplo de CSS (archivo assets/css/doc-layout.css):

/ Estructura base /
.dls-wrapper {
  display: grid
  grid-template-columns: 280px 1fr
  gap: 32px
  align-items: start
}

/ Índice lateral /
.dls-toc {
  position: relative
}
.dls-toc ul {
  list-style: none
  margin: 0
  padding: 0
}
.dls-toc a {
  display: block
  padding: 8px 12px
  color: #1a1a1a
  text-decoration: none
  border-radius: 4px
}
.dls-toc a:hover,
.dls-toc a:focus {
  background: rgba(0,0,0,0.05)
  outline: none
}

/ Sticky: posición con respect al contenedor principal /
.dls-toc {
  position: sticky
  top: 20px / ajustar si hay header fijo /
}

/ Enlace activo /
.dls-toc a.active {
  background: #0073aa
  color: #ffffff
  font-weight: 600
}

/ Contenido /
.dls-content {
  min-width: 0
}
.dls-content section {
  margin-bottom: 40px
}

/ Responsive: en pantallas pequeñas apilamos /
@media (max-width: 900px) {
  .dls-wrapper {
    grid-template-columns: 1fr
  }
  .dls-toc {
    position: relative
    top: 0
    margin-bottom: 16px
  }
  / Opcional: hacer el TOC colapsable con un botón usando JS /
}

4) JavaScript: scroll suave y scrollspy con Intersection Observer

Usar Intersection Observer es más eficiente que escuchar scroll y calcular offsets constantemente. También implementaremos scroll suave y manejo de offset (para header fijo).

Ejemplo de JS (assets/js/doc-layout.js):

document.addEventListener(DOMContentLoaded, function () {
  var toc = document.querySelector(.dls-toc)
  if (!toc) return

  var links = Array.prototype.slice.call(toc.querySelectorAll(a[href^=#]))
  var sections = links.map(function (link) {
    var id = link.getAttribute(href).slice(1)
    return document.getElementById(id)
  }).filter(Boolean)

  var scrollOffset = (typeof dlsConfig !== undefined  dlsConfig.scrollOffset) ? parseInt(dlsConfig.scrollOffset, 10) : 0

  // Scroll suave
  links.forEach(function (link) {
    link.addEventListener(click, function (e) {
      var targetId = this.getAttribute(href).slice(1)
      var target = document.getElementById(targetId)
      if (!target) return
      e.preventDefault()
      var targetTop = target.getBoundingClientRect().top   window.pageYOffset - scrollOffset
      window.scrollTo({ top: targetTop, behavior: smooth })

      // Mover foco al heading por accesibilidad
      var heading = target.querySelector(h1, h2, h3, h4, h5, h6)
      if (heading) {
        heading.setAttribute(tabindex, -1)
        heading.focus()
        // opcional: remover tabindex después
        heading.addEventListener(blur, function () {
          heading.removeAttribute(tabindex)
        }, { once: true })
      }
    }, false)
  })

  // Intersection Observer para resaltar item activo
  var observerOptions = {
    root: null,
    rootMargin: -   Math.max( (scrollOffset - 10), 0 )   px 0px -40% 0px,
    threshold: [0, 0.25, 0.5, 0.75, 1]
  }

  var activeLink = null

  var observer = new IntersectionObserver(function (entries) {
    entries.forEach(function (entry) {
      if (entry.isIntersecting) {
        var id = entry.target.id
        var newActive = toc.querySelector(a[href=#   id   ])
        if (newActive  newActive !== activeLink) {
          if (activeLink) activeLink.classList.remove(active)
          newActive.classList.add(active)
          activeLink = newActive
        }
      }
    })
  }, observerOptions)

  sections.forEach(function (section) {
    observer.observe(section)
  })
})

5) Accesibilidad y usabilidad

Aspectos a tener en cuenta:

6) Generar el índice automáticamente (opcional pero recomendable)

Para no tener que escribir manualmente el índice, puedes parsear el contenido (o usar las cabeceras del post) y construir la lista. Aquí un enfoque sencillo en PHP para convertir los h2/h3 de un contenido en una lista de enlaces:

function dls_generate_toc_from_content( content ) {
    dom = new DOMDocument()
    libxml_use_internal_errors(true)
    dom->loadHTML(  . content )
    libxml_clear_errors()
    headings = dom->getElementsByTagName()
    toc = array()
    foreach ( headings as node ) {
        tag = strtolower( node->nodeName )
        if ( in_array( tag, array( h2, h3 ) ) ) {
            text = trim( node->textContent )
            id = node->getAttribute(id)
            if ( ! id ) {
                id = sanitize_title( text )
                node->setAttribute( id, id )
            }
            toc[] = array( id => id, text => text, tag => tag )
        }
    }
    // Devolver el contenido modificado y el TOC como array
    return array( content => dom->saveHTML(), toc => toc )
}

Nota: cuando se usa DOMDocument con HTML generado por WordPress, hay que manejar encoding y errores. Otra opción es usar expresiones regulares simples, aunque menos seguras.

7) Seguridad y buenas prácticas en WordPress

8) Variantes y mejoras

  1. Hacer el TOC colapsable en móviles: añadir un botón que alterne la clase .is-open y muestre/oculte el nav.
  2. Permitir anidado de enlaces (h2 > h3) visualmente indentado.
  3. Crear un bloque de Gutenberg personalizado para insertar el layout con controles visuales.
  4. Cachear la generación automática del TOC para evitar procesamiento en cada carga.
  5. Agregar enlaces copy link en cada heading para permitir copiar el anchor fácilmente.

9) Ejemplo de shortcode completo con TOC dinámico (esqueleto)

Patrón general: parsea el contenido o una plantilla, genera TOC, renderiza nav con enlaces y el contenido con ids. Aquí sólo se muestra la idea de integración (sustituye según tus necesidades):

function dls_docs_shortcode_dynamic( atts ) {
    // Obtener contenido desde un archivo plantilla o desde post->post_content
    content = file_get_contents( plugin_dir_path( __FILE__ ) . template/docs-sample.html )
    parsed = dls_generate_toc_from_content( content )
    content_with_ids = parsed[content]
    toc_items = parsed[toc]

    ob_start()
    ?>
    

10) Pruebas y depuración

  • Probar en distintos navegadores y dispositivos (mobile/desktop).
  • Comprobar que el índice no queda tapado por headers fijos.
  • Verificar que los enlaces funcionan con teclados y lectores de pantalla.
  • Usar Lighthouse o herramientas de accesibilidad para mejorar contraste y navegación.

Conclusión

Con los elementos mostrados puedes construir un sistema de documentación dentro de WordPress con un índice sticky funcional, accesible y responsivo. La versión básica es rápida de implementar: registra assets, genera el HTML (ya sea manual o dinámicamente), aplica CSS para layout y position: sticky, y añade un JS ligero con Intersection Observer para mejorar la experiencia. Desde ahí puedes evolucionar hacia un bloque de Gutenberg, generación automática más sofisticada, y opciones de personalización desde el admin.



Leave a Reply

Your email address will not be published. Required fields are marked *