Ir al contenido
Evaluar mi proyecto
Componentes · MicrointeraccionesDisponible

Dock de navegación con aumento

Dock de enlaces con aumento del elemento activo y sus vecinos; tooltip en hover o foco, y navegación táctil estable.

Código completo

Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.

dock-interactivo.tsx105 líneas
Descargar
"use client";

import { useState, type CSSProperties, type ReactNode } from "react";

/** Dock de navegación con aumento. React + TypeScript, sin dependencias. Personaliza con variables --gczd-*. */
export type DockItem = { label: string; href: string; icon?: ReactNode };
export type DockInteractivoProps = {
  items?: DockItem[];
  className?: string;
  style?: CSSProperties;
};

const CSS = `
.gczd-root { box-sizing: border-box; min-width: 0; max-width: 100%; color: var(--gczd-fg, #202421); font: 400 15px/1.5 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
.gczd-root *, .gczd-root *::before, .gczd-root *::after { box-sizing: border-box; }
.gczd-root button, .gczd-root input, .gczd-root textarea, .gczd-root select { font: inherit; }
.gczd-root button, .gczd-root a { -webkit-tap-highlight-color: transparent; }
.gczd-root :focus-visible { outline: 2px solid var(--gczd-accent, #25724b); outline-offset: 4px; }
.gczd-root ::selection { background: var(--gczd-accent, #25724b); color: #fff; }
.gczd-root button:disabled { opacity: .5; cursor: not-allowed; }
.gczd-root svg { flex-shrink: 0; }
@media (prefers-reduced-motion: reduce) { .gczd-root *, .gczd-root *::before, .gczd-root *::after { animation: none !important; transition-duration: .01ms !important; scroll-behavior: auto !important; } }
.gczd-root { display:flex; flex-wrap:wrap; gap:8px; align-items:flex-end; justify-content:center; padding:12px; border:1px solid #ccd4c9; border-radius:16px; background:var(--gczd-bg,#f2f6ee); }
.gczd-root a { position:relative; width:48px; height:48px; display:grid; place-items:center; border-radius:10px; color:#315138; background:#dfe8d9; text-decoration:none; }
.gczd-icon { display:grid; place-items:center; width:100%; height:100%; background:#dfe8d9; border-radius:10px; transform-origin:center bottom; transition:transform .3s cubic-bezier(.16,1,.3,1),background .2s; }
.gczd-root a[data-distance="active"] .gczd-icon { transform:translateY(-8px) scale(1.2); background:#c6d6bb; }
.gczd-root a[data-distance="near"] .gczd-icon { transform:translateY(-3px) scale(1.05); }
.gczd-tooltip { position:absolute; bottom:calc(100% + 23px); left:50%; transform:translateX(-50%) translateY(4px); background:#2f472e; color:#fff; padding:5px 9px; border-radius:4px; font-size:11px; opacity:0; pointer-events:none; white-space:nowrap; transition:opacity .15s,transform .15s; }
.gczd-root a[data-distance="active"] .gczd-tooltip { opacity:1; transform:translateX(-50%) translateY(0); }
@media(prefers-reduced-motion:reduce) { .gczd-icon { transform:none!important; } }`;

export function DockInteractivo({
  items = [
    { label: "Inicio", href: "#inicio" },
    { label: "Proyectos", href: "#proyectos" },
    { label: "Archivos", href: "#archivos" },
    { label: "Mensajes", href: "#mensajes" },
    { label: "Ajustes", href: "#ajustes" },
  ],
  className = "",
  style,
}: DockInteractivoProps) {
  const [active, setActive] = useState<number | null>(null);
  const paths = [
    "M3 10 12 3l9 7v10H3ZM9 20v-7h6v7",
    "M3 5h7l2 3h9v12H3Z",
    "M6 3h8l4 4v14H6ZM14 3v5h4M9 13h6M9 17h6",
    "M3 4h18v13H8l-5 4ZM7 8h10M7 12h6",
    "M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8M12 2v3M12 19v3M2 12h3M19 12h3M5 5l2 2M17 17l2 2M19 5l-2 2M7 17l-2 2",
  ];
  return (
    <nav
      className={`gczd-root ${className}`}
      style={style}
      aria-label="Navegación rápida"
      onPointerLeave={() => setActive(null)}
    >
      <style>{CSS}</style>
      {items.map((item, i) => (
        <a
          key={item.href}
          href={item.href}
          aria-label={item.label}
          title={item.label}
          data-distance={
            active === null
              ? "far"
              : Math.abs(active - i) > 1
                ? "far"
                : active === i
                  ? "active"
                  : "near"
          }
          onPointerEnter={(e) => {
            if (e.pointerType === "mouse") setActive(i);
          }}
          onFocus={() => setActive(i)}
          onBlur={() => setActive(null)}
        >
          <span className="gczd-tooltip" aria-hidden="true">
            {item.label}
          </span>
          <span className="gczd-icon">
            {item.icon ?? (
              <svg
                width="24"
                height="24"
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                strokeWidth="1.5"
                strokeLinecap="round"
                strokeLinejoin="round"
                aria-hidden="true"
              >
                <path d={paths[i % paths.length]} />
              </svg>
            )}
          </span>
        </a>
      ))}
    </nav>
  );
}

Úsalo en tu proyecto

Instálalo con la CLI de shadcn.

terminal.sh1 líneas
npx shadcn@latest add https://www.gassierconsulting.com/r/dock-interactivo

Ejemplo de uso de Dock de navegación con aumento

Después de copiar dock-interactivo.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.

ejemplo.tsx5 líneas
import { DockInteractivo } from "./dock-interactivo";

export default function Ejemplo() {
  return <DockInteractivo />;
}

Propiedades de DockInteractivo

DockInteractivo acepta 3 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.

PropiedadTipoDescripción
items?DockItem[]Opcional.
className?stringOpcional.
style?CSSPropertiesOpcional.

Personaliza Dock de navegación con aumento con variables CSS

Define estas variables en el contenedor o en tu hoja de estilos para cambiar los colores sin tocar el componente. Si no las defines, se usa el valor por defecto.

VariableValor por defecto
--gczd-fg#202421
--gczd-accent#25724b
--gczd-bg#f2f6ee

Accesibilidad y rendimiento

El archivo tiene 105 líneas (5 KB) y no necesita @keyframes. Esto es lo que hace Dock de navegación con aumento por quien lo usa:

  • Detecta prefers-reduced-motion y baja o quita el movimiento cuando la persona lo pide.
  • Muestra un anillo de foco visible al navegar con teclado (:focus-visible).
  • Expone su estado a tecnologías de apoyo con atributos aria-*.
  • Es un componente de cliente de Next.js: incluye la directiva "use client".
Licencia
MIT
Dependencias
React >=18
Compatibilidad
React 18 y 19 · Next.js (componente de cliente)