UNPKG

reactbits-mcp-server

Version:

MCP Server for React Bits - Access 99+ React components with animations, backgrounds, and UI elements

260 lines (231 loc) 7.59 kB
import { useEffect, useRef, useId } from "react"; import "./GlassSurface.css"; const GlassSurface = ({ children, width = 200, height = 80, borderRadius = 20, borderWidth = 0.07, brightness = 50, opacity = 0.93, blur = 11, displace = 0, backgroundOpacity = 0, saturation = 1, distortionScale = -180, redOffset = 0, greenOffset = 10, blueOffset = 20, xChannel = "R", yChannel = "G", mixBlendMode = "difference", className = "", style = {}, }) => { const uniqueId = useId().replace(/:/g, '-'); const filterId = `glass-filter-${uniqueId}`; const redGradId = `red-grad-${uniqueId}`; const blueGradId = `blue-grad-${uniqueId}`; const containerRef = useRef(null); const feImageRef = useRef(null); const redChannelRef = useRef(null); const greenChannelRef = useRef(null); const blueChannelRef = useRef(null); const gaussianBlurRef = useRef(null); const generateDisplacementMap = () => { const rect = containerRef.current?.getBoundingClientRect(); const actualWidth = rect?.width || 400; const actualHeight = rect?.height || 200; const edgeSize = Math.min(actualWidth, actualHeight) * (borderWidth * 0.5); const svgContent = ` <svg viewBox="0 0 ${actualWidth} ${actualHeight}" xmlns="http://www.w3.org/2000/svg"> <defs> <linearGradient id="${redGradId}" x1="100%" y1="0%" x2="0%" y2="0%"> <stop offset="0%" stop-color="#0000"/> <stop offset="100%" stop-color="red"/> </linearGradient> <linearGradient id="${blueGradId}" x1="0%" y1="0%" x2="0%" y2="100%"> <stop offset="0%" stop-color="#0000"/> <stop offset="100%" stop-color="blue"/> </linearGradient> </defs> <rect x="0" y="0" width="${actualWidth}" height="${actualHeight}" fill="black"></rect> <rect x="0" y="0" width="${actualWidth}" height="${actualHeight}" rx="${borderRadius}" fill="url(#${redGradId})" /> <rect x="0" y="0" width="${actualWidth}" height="${actualHeight}" rx="${borderRadius}" fill="url(#${blueGradId})" style="mix-blend-mode: ${mixBlendMode}" /> <rect x="${edgeSize}" y="${edgeSize}" width="${actualWidth - edgeSize * 2}" height="${actualHeight - edgeSize * 2}" rx="${borderRadius}" fill="hsl(0 0% ${brightness}% / ${opacity})" style="filter:blur(${blur}px)" /> </svg> `; return `data:image/svg+xml,${encodeURIComponent(svgContent)}`; }; const updateDisplacementMap = () => { feImageRef.current?.setAttribute("href", generateDisplacementMap()); }; useEffect(() => { updateDisplacementMap(); [ { ref: redChannelRef, offset: redOffset }, { ref: greenChannelRef, offset: greenOffset }, { ref: blueChannelRef, offset: blueOffset }, ].forEach(({ ref, offset }) => { if (ref.current) { ref.current.setAttribute( "scale", (distortionScale + offset).toString() ); ref.current.setAttribute("xChannelSelector", xChannel); ref.current.setAttribute("yChannelSelector", yChannel); } }); gaussianBlurRef.current?.setAttribute("stdDeviation", displace.toString()); // eslint-disable-next-line react-hooks/exhaustive-deps }, [ width, height, borderRadius, borderWidth, brightness, opacity, blur, displace, distortionScale, redOffset, greenOffset, blueOffset, xChannel, yChannel, mixBlendMode, ]); useEffect(() => { if (!containerRef.current) return; const resizeObserver = new ResizeObserver(() => { setTimeout(updateDisplacementMap, 0); }); resizeObserver.observe(containerRef.current); return () => { resizeObserver.disconnect(); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { if (!containerRef.current) return; const resizeObserver = new ResizeObserver(() => { setTimeout(updateDisplacementMap, 0); }); resizeObserver.observe(containerRef.current); return () => { resizeObserver.disconnect(); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { setTimeout(updateDisplacementMap, 0); // eslint-disable-next-line react-hooks/exhaustive-deps }, [width, height]); const supportsSVGFilters = () => { const isWebkit = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent); const isFirefox = /Firefox/.test(navigator.userAgent); if (isWebkit || isFirefox) { return false; } const div = document.createElement("div"); div.style.backdropFilter = `url(#${filterId})`; return div.style.backdropFilter !== ""; }; const containerStyle = { ...style, width: typeof width === "number" ? `${width}px` : width, height: typeof height === "number" ? `${height}px` : height, borderRadius: `${borderRadius}px`, "--glass-frost": backgroundOpacity, "--glass-saturation": saturation, "--filter-id": `url(#${filterId})`, }; return ( <div ref={containerRef} className={`glass-surface ${supportsSVGFilters() ? "glass-surface--svg" : "glass-surface--fallback"} ${className}`} style={containerStyle} > <svg className="glass-surface__filter" xmlns="http://www.w3.org/2000/svg"> <defs> <filter id={filterId} colorInterpolationFilters="sRGB" x="0%" y="0%" width="100%" height="100%" > <feImage ref={feImageRef} x="0" y="0" width="100%" height="100%" preserveAspectRatio="none" result="map" /> <feDisplacementMap ref={redChannelRef} in="SourceGraphic" in2="map" id="redchannel" result="dispRed" /> <feColorMatrix in="dispRed" type="matrix" values="1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" result="red" /> <feDisplacementMap ref={greenChannelRef} in="SourceGraphic" in2="map" id="greenchannel" result="dispGreen" /> <feColorMatrix in="dispGreen" type="matrix" values="0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0" result="green" /> <feDisplacementMap ref={blueChannelRef} in="SourceGraphic" in2="map" id="bluechannel" result="dispBlue" /> <feColorMatrix in="dispBlue" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0" result="blue" /> <feBlend in="red" in2="green" mode="screen" result="rg" /> <feBlend in="rg" in2="blue" mode="screen" result="output" /> <feGaussianBlur ref={gaussianBlurRef} in="output" stdDeviation="0.7" /> </filter> </defs> </svg> <div className="glass-surface__content">{children}</div> </div> ); }; export default GlassSurface;