Footer postal con nota editable
Footer con composición postal y textarea real que prepara un correo; el usuario revisa y envía desde su aplicación de correo.
Código completo
Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.
"use client";
import { useState, useId, type CSSProperties } from "react";
/** Footer postal con nota editable. React + TypeScript, sin dependencias. Personaliza con variables --gcyn-*. */
export type FooterPostalProps = {
brand?: string;
email?: string;
title?: string;
className?: string;
style?: CSSProperties;
};
const CSS = `
.gcyn-root { box-sizing: border-box; min-width: 0; max-width: 100%; color: var(--gcyn-fg, #202421); font: 400 15px/1.5 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
.gcyn-root *, .gcyn-root *::before, .gcyn-root *::after { box-sizing: border-box; }
.gcyn-root button, .gcyn-root input, .gcyn-root textarea, .gcyn-root select { font: inherit; }
.gcyn-root button, .gcyn-root a { -webkit-tap-highlight-color: transparent; }
.gcyn-root :focus-visible { outline: 2px solid var(--gcyn-accent, #25724b); outline-offset: 4px; }
.gcyn-root ::selection { background: var(--gcyn-accent, #25724b); color: #fff; }
.gcyn-root button:disabled { opacity: .5; cursor: not-allowed; }
.gcyn-root svg { flex-shrink: 0; }
@media (prefers-reduced-motion: reduce) { .gcyn-root *, .gcyn-root *::before, .gcyn-root *::after { animation: none !important; transition-duration: .01ms !important; scroll-behavior: auto !important; } }
.gcyn-root { width:100%; display:grid; grid-template-columns:1fr 1fr; gap:48px; background:var(--gcyn-bg,#e4d8c6); color:var(--gcyn-fg,#4c3a2d); padding:56px clamp(24px,5vw,64px); container-type:inline-size; }
.gcyn-left h2 { font-size:clamp(36px,4.5vw,58px); line-height:1.08; letter-spacing:-.03em; font-weight:600; margin:0 0 24px; text-wrap:balance; }
.gcyn-left p { font-size:15px; color:#6c5947; margin:0 0 60px; }
.gcyn-left strong { font-size:18px; font-weight:500; }
.gcyn-card { position:relative; padding:32px; background:#faf4e8; border-radius:4px; box-shadow:0 14px 32px #6f493b16; min-width:0; transform:rotate(1deg); }
.gcyn-stamp { position:absolute; top:20px; right:20px; color:#9b694b; }
.gcyn-card label { font-weight:600; font-size:13px; display:block; margin-bottom:18px; }
.gcyn-card textarea { resize:vertical; min-height:140px; width:100%; border:0; border-bottom:1px solid #cabb9f; padding:16px 0; margin:14px 0 16px; background:transparent; color:#4c3a2d; font-size:17px; line-height:1.6; }
.gcyn-card textarea::placeholder { color:#81705c; }
.gcyn-card a { display:flex; align-items:center; justify-content:space-between; color:#624128; min-height:44px; text-underline-offset:4px; font-size:13px; font-weight:600; }
.gcyn-card small { display:block; font-size:11px; color:#6e5e4d; margin-top:16px; }
@container(max-width:600px) { .gcyn-left h2 { font-size:40px; } }
@media(max-width:620px) { .gcyn-root { grid-template-columns:1fr; gap:30px; }
.gcyn-left p { margin-bottom:24px; }
.gcyn-card { padding:24px; transform:none; } }`;
export function FooterPostal({
brand = "Correspondencia",
email = "hola@example.com",
title = "Una idea empieza con una nota.",
className = "",
style,
}: FooterPostalProps) {
const [note, setNote] = useState("");
const id = useId();
const href = `mailto:${email}?subject=${encodeURIComponent("Una nueva idea")}&body=${encodeURIComponent(note)}`;
return (
<footer className={`gcyn-root ${className}`} style={style}>
<style>{CSS}</style>
<div className="gcyn-left">
<h2>{title}</h2>
<p>
Escribe unas líneas.
<br />
El resto lo conversamos.
</p>
<strong>{brand}</strong>
</div>
<div className="gcyn-card">
<svg
className="gcyn-stamp"
width="42"
height="48"
viewBox="0 0 42 48"
fill="none"
stroke="currentColor"
strokeWidth="1"
aria-hidden="true"
>
<rect x="3" y="3" width="36" height="42" strokeDasharray="2 3" />
<path d="M10 15h22M10 22h22M10 29h15M10 36h22" />
</svg>
<label htmlFor={id}>Tu nota</label>
<textarea
id={id}
value={note}
onChange={(e) => setNote(e.target.value)}
placeholder="Tengo una idea para…"
maxLength={1500}
/>
<a href={href}>
Abrir en mi correo
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
aria-hidden="true"
>
<path d="M5 12h14m-6-6 6 6-6 6" />
</svg>
</a>
<small>
Se abrirá tu aplicación de correo. No se envía automáticamente.
</small>
</div>
</footer>
);
}
Úsalo en tu proyecto
Instálalo con la CLI de shadcn.
npx shadcn@latest add https://www.gassierconsulting.com/r/footer-postalEjemplo de uso de Footer postal con nota editable
Después de copiar footer-postal.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.
import { FooterPostal } from "./footer-postal";
export default function Ejemplo() {
return <FooterPostal brand="Ejemplo" />;
}Propiedades de FooterPostal
FooterPostal acepta 5 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.
| Propiedad | Tipo | Descripción |
|---|---|---|
brand? | string | Opcional. |
email? | string | Opcional. |
title? | string | Opcional. |
className? | string | Opcional. |
style? | CSSProperties | Opcional. |
Personaliza Footer postal con nota editable 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.
| Variable | Valor por defecto |
|---|---|
--gcyn-fg | #202421 |
--gcyn-accent | #25724b |
--gcyn-bg | #e4d8c6 |
Accesibilidad y rendimiento
El archivo tiene 105 líneas (5 KB) y no necesita @keyframes. Esto es lo que hace Footer postal con nota editable 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".