Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions apps/www/content/blog/tailwind-css-button.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ const baseClasses = 'font-semibold rounded-lg shadow-md focus:outline-none focus

// The final className will be built conditionally
return (

<button className={...} {...props}>
{children}
</button>
Expand Down Expand Up @@ -264,11 +265,11 @@ clsx(baseClasses, variantClasses[variant], sizeClasses[size], className)
);

return (

<button className={finalClasses} {...props}>
{children}
{children}
</button>
);
}
); }

And there you have it. This reusable `Button` component is now the foundation for a scalable, maintainable UI system. You can call it anywhere in your app, pass a few props, and get a perfectly styled, consistent button every time.

Expand Down
3 changes: 3 additions & 0 deletions apps/www/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,19 @@
"@radix-ui/react-tooltip": "^1.2.8",
"@tabler/icons-react": "^3.34.1",
"@tailwindcss/postcss": "^4.1.13",
"@types/lodash.debounce": "^4.0.9",
"canvas-confetti": "^1.9.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"cobe": "^0.6.4",
"framer-motion": "^12.23.24",
"fumadocs-core": "^15.8.2",
"fumadocs-mdx": "^12.0.1",
"fumadocs-ui": "^15.7.12",
"geist": "^1.5.1",
"jotai": "^2.14.0",
"lodash.debounce": "^4.0.8",
"lucide-react": "^0.544.0",
"masonic": "^4.1.0",
"motion": "^12.23.12",
Expand Down
95 changes: 43 additions & 52 deletions apps/www/public/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2338,12 +2338,13 @@ Description: A animated background grid pattern made with SVGs, fully customizab

import {
ComponentPropsWithoutRef,
useCallback,
useEffect,
useId,
useRef,
useState,
} from "react"
import { motion } from "motion/react"
// Use native SVG <animate> for per-rect opacity animation (lighter than framer-motion)

import { cn } from "@/lib/utils"

Expand Down Expand Up @@ -2373,47 +2374,33 @@ export function AnimatedGridPattern({
...props
}: AnimatedGridPatternProps) {
const id = useId()
const containerRef = useRef(null)
const containerRef = useRef<SVGSVGElement | null>(null)
const [dimensions, setDimensions] = useState({ width: 0, height: 0 })
const [squares, setSquares] = useState(() => generateSquares(numSquares))

function getPos() {
const getPos = useCallback(() => {
return [
Math.floor((Math.random() * dimensions.width) / width),
Math.floor((Math.random() * dimensions.height) / height),
]
}
}, [dimensions, width, height])

// Adjust the generateSquares function to return objects with an id, x, and y
function generateSquares(count: number) {
return Array.from({ length: count }, (_, i) => ({
id: i,
pos: getPos(),
}))
}
const generateSquares = useCallback(
(count: number) =>
Array.from({ length: count }, (_, i) => ({
id: i,
pos: getPos(),
})),
[getPos]
)

// Function to update a single square's position
const updateSquarePosition = (id: number) => {
setSquares((currentSquares) =>
currentSquares.map((sq) =>
sq.id === id
? {
...sq,
pos: getPos(),
}
: sq
)
)
}
const [squares, setSquares] = useState(() => generateSquares(numSquares))

// Update squares to animate in
useEffect(() => {
if (dimensions.width && dimensions.height) {
setSquares(generateSquares(numSquares))
}
}, [dimensions, numSquares, generateSquares])

// Resize observer to update container dimensions
useEffect(() => {
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
Expand All @@ -2424,16 +2411,13 @@ export function AnimatedGridPattern({
}
})

if (containerRef.current) {
resizeObserver.observe(containerRef.current)
}

const node = containerRef.current
if (node) resizeObserver.observe(node)
return () => {
if (containerRef.current) {
resizeObserver.unobserve(containerRef.current)
}
// Disconnect the observer to fully release resources and avoid stale references
resizeObserver.disconnect()
}
}, [containerRef])
}, [])

return (
<svg
Expand Down Expand Up @@ -2461,29 +2445,36 @@ export function AnimatedGridPattern({
/>
</pattern>
</defs>

{/* background grid */}
<rect width="100%" height="100%" fill={`url(#${id})`} />
<svg x={x} y={y} className="overflow-visible">
{squares.map(({ pos: [x, y], id }, index) => (
<motion.rect
initial={{ opacity: 0 }}
animate={{ opacity: maxOpacity }}
transition={{
duration,
repeat: 1,
delay: index * 0.1,
repeatType: "reverse",
}}
onAnimationComplete={() => updateSquarePosition(id)}
key={`${x}-${y}-${index}`}

{/* animated squares */}
<g transform={`translate(${x}, ${y})`}>
{squares.map(({ pos: [px, py] }, index) => (
<rect
key={index}
width={width - 1}
height={height - 1}
x={x * width + 1}
y={y * height + 1}
x={px * width + 1}
y={py * height + 1}
fill="currentColor"
strokeWidth="0"
/>
opacity={0}
>
{/* Use native SVG animation for opacity. This is much lighter than
mounting many JS-driven animation nodes (framer-motion) and
keeps animation on the compositor where possible. */}
<animate
attributeName="opacity"
values={`0;${maxOpacity};0`}
dur={`${duration}s`}
begin={`${(index * 0.1).toFixed(2)}s`}
repeatCount="indefinite"
/>
</rect>
))}
</svg>
</g>
</svg>
)
}
Expand Down
2 changes: 1 addition & 1 deletion apps/www/public/r/animated-grid-pattern.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"files": [
{
"path": "registry/magicui/animated-grid-pattern.tsx",
"content": "\"use client\"\n\nimport {\n ComponentPropsWithoutRef,\n useEffect,\n useId,\n useRef,\n useState,\n} from \"react\"\nimport { motion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface AnimatedGridPatternProps\n extends ComponentPropsWithoutRef<\"svg\"> {\n width?: number\n height?: number\n x?: number\n y?: number\n strokeDasharray?: number\n numSquares?: number\n maxOpacity?: number\n duration?: number\n repeatDelay?: number\n}\n\nexport function AnimatedGridPattern({\n width = 40,\n height = 40,\n x = -1,\n y = -1,\n strokeDasharray = 0,\n numSquares = 50,\n className,\n maxOpacity = 0.5,\n duration = 4,\n ...props\n}: AnimatedGridPatternProps) {\n const id = useId()\n const containerRef = useRef(null)\n const [dimensions, setDimensions] = useState({ width: 0, height: 0 })\n const [squares, setSquares] = useState(() => generateSquares(numSquares))\n\n function getPos() {\n return [\n Math.floor((Math.random() * dimensions.width) / width),\n Math.floor((Math.random() * dimensions.height) / height),\n ]\n }\n\n // Adjust the generateSquares function to return objects with an id, x, and y\n function generateSquares(count: number) {\n return Array.from({ length: count }, (_, i) => ({\n id: i,\n pos: getPos(),\n }))\n }\n\n // Function to update a single square's position\n const updateSquarePosition = (id: number) => {\n setSquares((currentSquares) =>\n currentSquares.map((sq) =>\n sq.id === id\n ? {\n ...sq,\n pos: getPos(),\n }\n : sq\n )\n )\n }\n\n // Update squares to animate in\n useEffect(() => {\n if (dimensions.width && dimensions.height) {\n setSquares(generateSquares(numSquares))\n }\n }, [dimensions, numSquares, generateSquares])\n\n // Resize observer to update container dimensions\n useEffect(() => {\n const resizeObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n setDimensions({\n width: entry.contentRect.width,\n height: entry.contentRect.height,\n })\n }\n })\n\n if (containerRef.current) {\n resizeObserver.observe(containerRef.current)\n }\n\n return () => {\n if (containerRef.current) {\n resizeObserver.unobserve(containerRef.current)\n }\n }\n }, [containerRef])\n\n return (\n <svg\n ref={containerRef}\n aria-hidden=\"true\"\n className={cn(\n \"pointer-events-none absolute inset-0 h-full w-full fill-gray-400/30 stroke-gray-400/30\",\n className\n )}\n {...props}\n >\n <defs>\n <pattern\n id={id}\n width={width}\n height={height}\n patternUnits=\"userSpaceOnUse\"\n x={x}\n y={y}\n >\n <path\n d={`M.5 ${height}V.5H${width}`}\n fill=\"none\"\n strokeDasharray={strokeDasharray}\n />\n </pattern>\n </defs>\n <rect width=\"100%\" height=\"100%\" fill={`url(#${id})`} />\n <svg x={x} y={y} className=\"overflow-visible\">\n {squares.map(({ pos: [x, y], id }, index) => (\n <motion.rect\n initial={{ opacity: 0 }}\n animate={{ opacity: maxOpacity }}\n transition={{\n duration,\n repeat: 1,\n delay: index * 0.1,\n repeatType: \"reverse\",\n }}\n onAnimationComplete={() => updateSquarePosition(id)}\n key={`${x}-${y}-${index}`}\n width={width - 1}\n height={height - 1}\n x={x * width + 1}\n y={y * height + 1}\n fill=\"currentColor\"\n strokeWidth=\"0\"\n />\n ))}\n </svg>\n </svg>\n )\n}\n",
"content": "\"use client\"\n\nimport {\n ComponentPropsWithoutRef,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n} from \"react\"\n\n// Use native SVG <animate> for per-rect opacity animation (lighter than framer-motion)\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface AnimatedGridPatternProps\n extends ComponentPropsWithoutRef<\"svg\"> {\n width?: number\n height?: number\n x?: number\n y?: number\n strokeDasharray?: number\n numSquares?: number\n maxOpacity?: number\n duration?: number\n repeatDelay?: number\n}\n\nexport function AnimatedGridPattern({\n width = 40,\n height = 40,\n x = -1,\n y = -1,\n strokeDasharray = 0,\n numSquares = 50,\n className,\n maxOpacity = 0.5,\n duration = 4,\n ...props\n}: AnimatedGridPatternProps) {\n const id = useId()\n const containerRef = useRef<SVGSVGElement | null>(null)\n const [dimensions, setDimensions] = useState({ width: 0, height: 0 })\n\n const getPos = useCallback(() => {\n return [\n Math.floor((Math.random() * dimensions.width) / width),\n Math.floor((Math.random() * dimensions.height) / height),\n ]\n }, [dimensions, width, height])\n\n const generateSquares = useCallback(\n (count: number) =>\n Array.from({ length: count }, (_, i) => ({\n id: i,\n pos: getPos(),\n })),\n [getPos]\n )\n\n const [squares, setSquares] = useState(() => generateSquares(numSquares))\n\n useEffect(() => {\n if (dimensions.width && dimensions.height) {\n setSquares(generateSquares(numSquares))\n }\n }, [dimensions, numSquares, generateSquares])\n\n useEffect(() => {\n const resizeObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n setDimensions({\n width: entry.contentRect.width,\n height: entry.contentRect.height,\n })\n }\n })\n\n const node = containerRef.current\n if (node) resizeObserver.observe(node)\n return () => {\n // Disconnect the observer to fully release resources and avoid stale references\n resizeObserver.disconnect()\n }\n }, [])\n\n return (\n <svg\n ref={containerRef}\n aria-hidden=\"true\"\n className={cn(\n \"pointer-events-none absolute inset-0 h-full w-full fill-gray-400/30 stroke-gray-400/30\",\n className\n )}\n {...props}\n >\n <defs>\n <pattern\n id={id}\n width={width}\n height={height}\n patternUnits=\"userSpaceOnUse\"\n x={x}\n y={y}\n >\n <path\n d={`M.5 ${height}V.5H${width}`}\n fill=\"none\"\n strokeDasharray={strokeDasharray}\n />\n </pattern>\n </defs>\n\n {/* background grid */}\n <rect width=\"100%\" height=\"100%\" fill={`url(#${id})`} />\n\n {/* animated squares */}\n <g transform={`translate(${x}, ${y})`}>\n {squares.map(({ pos: [px, py] }, index) => (\n <rect\n key={index}\n width={width - 1}\n height={height - 1}\n x={px * width + 1}\n y={py * height + 1}\n fill=\"currentColor\"\n strokeWidth=\"0\"\n opacity={0}\n >\n {/* Use native SVG animation for opacity. This is much lighter than\n mounting many JS-driven animation nodes (framer-motion) and\n keeps animation on the compositor where possible. */}\n <animate\n attributeName=\"opacity\"\n values={`0;${maxOpacity};0`}\n dur={`${duration}s`}\n begin={`${(index * 0.1).toFixed(2)}s`}\n repeatCount=\"indefinite\"\n />\n </rect>\n ))}\n </g>\n </svg>\n )\n}\n",
"type": "registry:ui"
}
]
Expand Down
93 changes: 40 additions & 53 deletions apps/www/registry/magicui/animated-grid-pattern.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

import {
ComponentPropsWithoutRef,
useCallback,
useEffect,
useId,
useRef,
useState,
} from "react"
import { motion } from "motion/react"

// Use native SVG <animate> for per-rect opacity animation (lighter than framer-motion)
import { cn } from "@/lib/utils"

export interface AnimatedGridPatternProps
Expand Down Expand Up @@ -37,47 +37,33 @@ export function AnimatedGridPattern({
...props
}: AnimatedGridPatternProps) {
const id = useId()
const containerRef = useRef(null)
const containerRef = useRef<SVGSVGElement | null>(null)
const [dimensions, setDimensions] = useState({ width: 0, height: 0 })
const [squares, setSquares] = useState(() => generateSquares(numSquares))

function getPos() {
const getPos = useCallback(() => {
return [
Math.floor((Math.random() * dimensions.width) / width),
Math.floor((Math.random() * dimensions.height) / height),
]
}
}, [dimensions, width, height])

// Adjust the generateSquares function to return objects with an id, x, and y
function generateSquares(count: number) {
return Array.from({ length: count }, (_, i) => ({
id: i,
pos: getPos(),
}))
}
const generateSquares = useCallback(
(count: number) =>
Array.from({ length: count }, (_, i) => ({
id: i,
pos: getPos(),
})),
[getPos]
)

// Function to update a single square's position
const updateSquarePosition = (id: number) => {
setSquares((currentSquares) =>
currentSquares.map((sq) =>
sq.id === id
? {
...sq,
pos: getPos(),
}
: sq
)
)
}
const [squares, setSquares] = useState(() => generateSquares(numSquares))

// Update squares to animate in
useEffect(() => {
if (dimensions.width && dimensions.height) {
setSquares(generateSquares(numSquares))
}
}, [dimensions, numSquares, generateSquares])

// Resize observer to update container dimensions
useEffect(() => {
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
Expand All @@ -88,16 +74,13 @@ export function AnimatedGridPattern({
}
})

if (containerRef.current) {
resizeObserver.observe(containerRef.current)
}

const node = containerRef.current
if (node) resizeObserver.observe(node)
return () => {
if (containerRef.current) {
resizeObserver.unobserve(containerRef.current)
}
// Fully disconnect to release all observers and avoid stale refs
resizeObserver.disconnect()
}
}, [containerRef])
}, [])

return (
<svg
Expand Down Expand Up @@ -125,29 +108,33 @@ export function AnimatedGridPattern({
/>
</pattern>
</defs>

{/* background grid */}
<rect width="100%" height="100%" fill={`url(#${id})`} />
<svg x={x} y={y} className="overflow-visible">
{squares.map(({ pos: [x, y], id }, index) => (
<motion.rect
initial={{ opacity: 0 }}
animate={{ opacity: maxOpacity }}
transition={{
duration,
repeat: 1,
delay: index * 0.1,
repeatType: "reverse",
}}
onAnimationComplete={() => updateSquarePosition(id)}
key={`${x}-${y}-${index}`}

{/* animated squares */}
<g transform={`translate(${x}, ${y})`}>
{squares.map(({ pos: [px, py] }, index) => (
<rect
key={`${px}-${py}-${index}`}
width={width - 1}
height={height - 1}
x={x * width + 1}
y={y * height + 1}
x={px * width + 1}
y={py * height + 1}
fill="currentColor"
strokeWidth="0"
/>
opacity={0}
>
<animate
attributeName="opacity"
values={`0;${maxOpacity};0`}
dur={`${duration}s`}
begin={`${(index * 0.1).toFixed(2)}s`}
repeatCount="indefinite"
/>
</rect>
))}
</svg>
</g>
</svg>
)
}
Loading