Header 1
Header 1 é um componente de layout para React e Tailwind CSS, da biblioteca SmoothUI, com licença MIT. Copia e cola no teu projeto.
SmoothUI
MIT
layout
O que é
Header 1 é um componente de layout para React e Tailwind CSS, da biblioteca SmoothUI, com licença MIT. Copia e cola no teu projeto.
Como usar
Abre o componente no catálogo, carrega em copiar, e cola no teu projeto. O prompt copiado leva o código e as regras para o teu agente o reproduzir sem alterar nada.
Cinco cópias grátis por mês. Sem cartão.
Precisa de
npm i lucide-react motion
Licença
Este componente vem de SmoothUI e é redistribuído sob a licença MIT, que permite uso comercial. O aviso de copyright viaja com o código.
Header 1
"use client";
import { ExternalLink } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { AnimatedGroup, AnimatedText, Button, HeroHeader } from "@/components/smoothui/shared";
import styles from "./hero-grid.module.css";
const CELL_SIZE = 120; // px
const COLORS = [
"oklch(0.72 0.2 352.53)", // blue
"#A764FF",
"#4B94FD",
"#FD4B4E",
"#FF8743",
];
function getRandomColor() {
return COLORS[Math.floor(Math.random() * COLORS.length)];
}
function SubGrid() {
const [cellColors, setCellColors] = useState<(string | null)[]>([
null,
null,
null,
null,
]);
// Add refs for leave timeouts
const leaveTimeouts = useRef<(NodeJS.Timeout | null)[]>([
null,
null,
null,
null,
]);
function handleHover(cellIdx: number) {
// Clear any pending timeout for this cell
const timeout = leaveTimeouts.current[cellIdx];
if (timeout) {
clearTimeout(timeout);
leaveTimeouts.current[cellIdx] = null;
}
setCellColors((prev) =>
prev.map((c, i) => (i === cellIdx ? getRandomColor() : c))
);
}
function handleLeave(cellIdx: number) {
// Add a small delay before removing the color
leaveTimeouts.current[cellIdx] = setTimeout(() => {
setCellColors((prev) => prev.map((c, i) => (i === cellIdx ? null : c)));
leaveTimeouts.current[cellIdx] = null;
}, 120);
}
// Cleanup on unmount
useEffect(
() => () => {
for (const t of leaveTimeouts.current) {
if (t) {
clearTimeout(t);
}
}
},
[]
);
return (
<div className={styles.subgrid} style={{ pointerEvents: "none" }}>
{[0, 1, 2, 3].map((cellIdx) => (
<button
className={styles.cell}
key={cellIdx}
onMouseEnter={() => handleHover(cellIdx)}
onMouseLeave={() => handleLeave(cellIdx)}
style={{
background: cellColors[cellIdx] || "transparent",
pointerEvents: "auto",
}}
type="button"
/>
))}
</div>
);
}
function InteractiveGrid() {
const containerRef = useRef<HTMLDivElement>(null);
const [grid, setGrid] = useState({ columns: 0, rows: 0 });
useEffect(() => {
function updateGrid() {
if (containerRef.current) {
const { width, height } = containerRef.current.getBoundingClientRect();
setGrid({
columns: Math.ceil(width / CELL_SIZE),
rows: Math.ceil(height / CELL_SIZE),
});
}
}
updateGrid();
window.addEventListener("resize", updateGrid);
return () => window.removeEventListener("resize", updateGrid);
}, []);
const total = grid.columns * grid.rows;
return (
<div
aria-hidden="true"
className="pointer-events-none absolute inset-0 z-0"
ref={containerRef}
style={{ height: "100%", width: "100%" }}
>
<div
className={styles.mainGrid}
style={
{
"--grid-cell-size": `${CELL_SIZE}px`,
gridTemplateColumns: `repeat(${grid.columns}, 1fr)`,
gridTemplateRows: `repeat(${grid.rows}, 1fr)`,
height: "100%",
width: "100%",
} as React.CSSProperties
}
>
{Array.from({ length: total }, (_, idx) => (
<SubGrid key={`subgrid-${grid.columns}-${grid.rows}-${idx}`} />
))}
</div>
</div>
);
}
export function HeroGrid() {
return (
<div className="relative">
<HeroHeader />
<main>
<section className="relative overflow-hidden py-36">
{/* Interactive animated grid background */}
<InteractiveGrid />
<AnimatedGroup
className="pointer-events-none flex flex-col items-center gap-6 text-center"
preset="blur-slide"
>
<div>
<AnimatedText
as="h1"
className="mb-6 text-pretty font-bold text-2xl tracking-tight lg:text-5xl"
>
Build your next project with{" "}
<span className="text-brand">Smoothui</span>
</AnimatedText>
<AnimatedText
as="p"
className="mx-auto max-w-3xl text-muted-foreground lg:text-xl"
delay={0.15}
>
Smoothui gives you the building blocks to create stunning,
animated interfaces in minutes.
</AnimatedText>
</div>
<AnimatedGroup
className="pointer-events-auto mt-6 flex justify-center gap-3"
preset="slide"
>
<Button
className="shadow-sm transition-shadow hover:shadow"
variant="outline"
>
Get Started
</Button>
<Button className="group" variant="candy">
Learn more{" "}
<ExternalLink className="ml-2 h-4 transition-transform group-hover:translate-x-0.5" />
</Button>
</AnimatedGroup>
</AnimatedGroup>
</section>
</main>
</div>
);
}
export default HeroGrid;
/* @reference "../../../../app/styles/smoothui.css"; */
.mainGrid {
position: absolute;
top: 0;
left: 0;
display: grid;
width: 100%;
height: 100%;
pointer-events: auto;
border-top: 1px solid var(--color-smooth-200);
border-right: 1px solid var(--color-smooth-200);
opacity: 1;
transition: opacity 300ms;
will-change: transform;
}
.subgrid {
position: relative;
display: grid;
grid-template-rows: repeat(2, 1fr);
grid-template-columns: repeat(2, 1fr);
width: var(--grid-cell-size);
height: var(--grid-cell-size);
pointer-events: auto;
user-select: none;
border-top: 1px solid var(--color-smooth-200);
border-right: 1px solid var(--color-smooth-200);
}
.cell {
width: 100%;
height: 100%;
pointer-events: auto;
transition: background 3000ms cubic-bezier(0.23, 1, 0.32, 1);
}
.cell:nth-child(1) {
border-right: 1px solid var(--color-smooth-200);
}
.cell:nth-child(3) {
border-top: 1px solid var(--color-smooth-200);
border-right: 1px solid var(--color-smooth-200);
}
.cell:nth-child(4) {
border-top: 1px solid var(--color-smooth-200);
}
/* Reduce overlay size of hero text/button containers */
.heroContent {
display: flex;
flex-direction: column;
align-items: flex-start;
max-width: 600px;
padding: 0;
margin: 0 auto;
background: none;
box-shadow: none;
}
.heroButtons {
display: flex;
gap: 1rem;
width: auto;
padding: 0;
margin-top: 1.5rem;
background: none;
box-shadow: none;
}