From ef03fc5f4bd774a7559b86b1487f3925ba2f1ab9 Mon Sep 17 00:00:00 2001 From: Lin Junrong Date: Fri, 31 Jul 2026 17:44:34 +0800 Subject: [PATCH 1/2] fix(GlassSurface): drop the duplicated resize effect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The component registers the same effect twice — byte-identical bodies, both creating a ResizeObserver on containerRef and calling updateDisplacementMap. So every instance runs two observers on one element and rebuilds the SVG displacement map twice on every resize. Removes the second copy. All four variants carry the same duplication. --- .../Components/GlassSurface/GlassSurface.jsx | 14 -------------- .../Components/GlassSurface/GlassSurface.jsx | 14 -------------- .../Components/GlassSurface/GlassSurface.tsx | 14 -------------- .../Components/GlassSurface/GlassSurface.tsx | 14 -------------- 4 files changed, 56 deletions(-) diff --git a/src/content/Components/GlassSurface/GlassSurface.jsx b/src/content/Components/GlassSurface/GlassSurface.jsx index 304c157ad..03f020a4c 100644 --- a/src/content/Components/GlassSurface/GlassSurface.jsx +++ b/src/content/Components/GlassSurface/GlassSurface.jsx @@ -117,20 +117,6 @@ const GlassSurface = ({ }; }, []); - useEffect(() => { - if (!containerRef.current) return; - - const resizeObserver = new ResizeObserver(() => { - setTimeout(updateDisplacementMap, 0); - }); - - resizeObserver.observe(containerRef.current); - - return () => { - resizeObserver.disconnect(); - }; - }, []); - useEffect(() => { setTimeout(updateDisplacementMap, 0); }, [width, height]); diff --git a/src/tailwind/Components/GlassSurface/GlassSurface.jsx b/src/tailwind/Components/GlassSurface/GlassSurface.jsx index 48e0d9462..f5435f92a 100644 --- a/src/tailwind/Components/GlassSurface/GlassSurface.jsx +++ b/src/tailwind/Components/GlassSurface/GlassSurface.jsx @@ -135,20 +135,6 @@ const GlassSurface = ({ }; }, []); - useEffect(() => { - if (!containerRef.current) return; - - const resizeObserver = new ResizeObserver(() => { - setTimeout(updateDisplacementMap, 0); - }); - - resizeObserver.observe(containerRef.current); - - return () => { - resizeObserver.disconnect(); - }; - }, []); - useEffect(() => { setTimeout(updateDisplacementMap, 0); }, [width, height]); diff --git a/src/ts-default/Components/GlassSurface/GlassSurface.tsx b/src/ts-default/Components/GlassSurface/GlassSurface.tsx index 795f6bba5..1c14166dc 100644 --- a/src/ts-default/Components/GlassSurface/GlassSurface.tsx +++ b/src/ts-default/Components/GlassSurface/GlassSurface.tsx @@ -157,20 +157,6 @@ const GlassSurface: React.FC = ({ }; }, []); - useEffect(() => { - if (!containerRef.current) return; - - const resizeObserver = new ResizeObserver(() => { - setTimeout(updateDisplacementMap, 0); - }); - - resizeObserver.observe(containerRef.current); - - return () => { - resizeObserver.disconnect(); - }; - }, []); - useEffect(() => { setTimeout(updateDisplacementMap, 0); }, [width, height]); diff --git a/src/ts-tailwind/Components/GlassSurface/GlassSurface.tsx b/src/ts-tailwind/Components/GlassSurface/GlassSurface.tsx index 4c7f3848f..93458101c 100644 --- a/src/ts-tailwind/Components/GlassSurface/GlassSurface.tsx +++ b/src/ts-tailwind/Components/GlassSurface/GlassSurface.tsx @@ -179,20 +179,6 @@ const GlassSurface: React.FC = ({ }; }, []); - useEffect(() => { - if (!containerRef.current) return; - - const resizeObserver = new ResizeObserver(() => { - setTimeout(updateDisplacementMap, 0); - }); - - resizeObserver.observe(containerRef.current); - - return () => { - resizeObserver.disconnect(); - }; - }, []); - useEffect(() => { setTimeout(updateDisplacementMap, 0); }, [width, height]); From afa0dba4a41e7f976906066008cec62d2ae9aa45 Mon Sep 17 00:00:00 2001 From: Lin Junrong Date: Mon, 3 Aug 2026 20:46:07 +0800 Subject: [PATCH 2/2] fix(GlassSurface): rebuild the registry artifacts public/r/*.json embeds the component source verbatim, and that is what the jsrepo CLI actually writes into a user's project. Fixing only the .tsx/.jsx files left the shipped payload with both copies of the effect, so installs would still get two ResizeObservers per instance. Regenerated with `npm run registry:build`; the four GlassSurface entries are the only ones that change. --- public/r/GlassSurface-JS-CSS.json | 2 +- public/r/GlassSurface-JS-TW.json | 2 +- public/r/GlassSurface-TS-CSS.json | 2 +- public/r/GlassSurface-TS-TW.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/public/r/GlassSurface-JS-CSS.json b/public/r/GlassSurface-JS-CSS.json index 016ee5309..81e8600bc 100644 --- a/public/r/GlassSurface-JS-CSS.json +++ b/public/r/GlassSurface-JS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "GlassSurface/GlassSurface.jsx", - "content": "/* eslint-disable react-hooks/exhaustive-deps */\nimport { useEffect, useState, useRef, useId } from 'react';\nimport './GlassSurface.css';\n\nconst GlassSurface = ({\n children,\n width = 200,\n height = 80,\n borderRadius = 20,\n borderWidth = 0.07,\n brightness = 50,\n opacity = 0.93,\n blur = 11,\n displace = 0,\n backgroundOpacity = 0,\n saturation = 1,\n distortionScale = -180,\n redOffset = 0,\n greenOffset = 10,\n blueOffset = 20,\n xChannel = 'R',\n yChannel = 'G',\n mixBlendMode = 'difference',\n className = '',\n style = {}\n}) => {\n const uniqueId = useId().replace(/:/g, '-');\n const filterId = `glass-filter-${uniqueId}`;\n const redGradId = `red-grad-${uniqueId}`;\n const blueGradId = `blue-grad-${uniqueId}`;\n\n const [svgSupported, setSvgSupported] = useState(false);\n\n const containerRef = useRef(null);\n const feImageRef = useRef(null);\n const redChannelRef = useRef(null);\n const greenChannelRef = useRef(null);\n const blueChannelRef = useRef(null);\n const gaussianBlurRef = useRef(null);\n\n const generateDisplacementMap = () => {\n const rect = containerRef.current?.getBoundingClientRect();\n const actualWidth = rect?.width || 400;\n const actualHeight = rect?.height || 200;\n const edgeSize = Math.min(actualWidth, actualHeight) * (borderWidth * 0.5);\n\n const svgContent = `\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n `;\n\n return `data:image/svg+xml,${encodeURIComponent(svgContent)}`;\n };\n\n const updateDisplacementMap = () => {\n feImageRef.current?.setAttribute('href', generateDisplacementMap());\n };\n\n useEffect(() => {\n updateDisplacementMap();\n [\n { ref: redChannelRef, offset: redOffset },\n { ref: greenChannelRef, offset: greenOffset },\n { ref: blueChannelRef, offset: blueOffset }\n ].forEach(({ ref, offset }) => {\n if (ref.current) {\n ref.current.setAttribute('scale', (distortionScale + offset).toString());\n ref.current.setAttribute('xChannelSelector', xChannel);\n ref.current.setAttribute('yChannelSelector', yChannel);\n }\n });\n\n gaussianBlurRef.current?.setAttribute('stdDeviation', displace.toString());\n }, [\n width,\n height,\n borderRadius,\n borderWidth,\n brightness,\n opacity,\n blur,\n displace,\n distortionScale,\n redOffset,\n greenOffset,\n blueOffset,\n xChannel,\n yChannel,\n mixBlendMode\n ]);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const resizeObserver = new ResizeObserver(() => {\n setTimeout(updateDisplacementMap, 0);\n });\n\n resizeObserver.observe(containerRef.current);\n\n return () => {\n resizeObserver.disconnect();\n };\n }, []);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const resizeObserver = new ResizeObserver(() => {\n setTimeout(updateDisplacementMap, 0);\n });\n\n resizeObserver.observe(containerRef.current);\n\n return () => {\n resizeObserver.disconnect();\n };\n }, []);\n\n useEffect(() => {\n setTimeout(updateDisplacementMap, 0);\n }, [width, height]);\n\n useEffect(() => {\n setSvgSupported(supportsSVGFilters());\n }, []);\n\n const supportsSVGFilters = () => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return false;\n }\n\n const isWebkit = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);\n const isFirefox = /Firefox/.test(navigator.userAgent);\n\n if (isWebkit || isFirefox) {\n return false;\n }\n\n const div = document.createElement('div');\n div.style.backdropFilter = `url(#${filterId})`;\n\n return div.style.backdropFilter !== '';\n };\n\n const containerStyle = {\n ...style,\n width: typeof width === 'number' ? `${width}px` : width,\n height: typeof height === 'number' ? `${height}px` : height,\n borderRadius: `${borderRadius}px`,\n '--glass-frost': backgroundOpacity,\n '--glass-saturation': saturation,\n '--filter-id': `url(#${filterId})`\n };\n\n return (\n \n \n \n \n \n\n \n \n\n \n \n\n \n \n\n \n \n \n \n \n \n\n
{children}
\n \n );\n};\n\nexport default GlassSurface;\n" + "content": "/* eslint-disable react-hooks/exhaustive-deps */\nimport { useEffect, useState, useRef, useId } from 'react';\nimport './GlassSurface.css';\n\nconst GlassSurface = ({\n children,\n width = 200,\n height = 80,\n borderRadius = 20,\n borderWidth = 0.07,\n brightness = 50,\n opacity = 0.93,\n blur = 11,\n displace = 0,\n backgroundOpacity = 0,\n saturation = 1,\n distortionScale = -180,\n redOffset = 0,\n greenOffset = 10,\n blueOffset = 20,\n xChannel = 'R',\n yChannel = 'G',\n mixBlendMode = 'difference',\n className = '',\n style = {}\n}) => {\n const uniqueId = useId().replace(/:/g, '-');\n const filterId = `glass-filter-${uniqueId}`;\n const redGradId = `red-grad-${uniqueId}`;\n const blueGradId = `blue-grad-${uniqueId}`;\n\n const [svgSupported, setSvgSupported] = useState(false);\n\n const containerRef = useRef(null);\n const feImageRef = useRef(null);\n const redChannelRef = useRef(null);\n const greenChannelRef = useRef(null);\n const blueChannelRef = useRef(null);\n const gaussianBlurRef = useRef(null);\n\n const generateDisplacementMap = () => {\n const rect = containerRef.current?.getBoundingClientRect();\n const actualWidth = rect?.width || 400;\n const actualHeight = rect?.height || 200;\n const edgeSize = Math.min(actualWidth, actualHeight) * (borderWidth * 0.5);\n\n const svgContent = `\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n `;\n\n return `data:image/svg+xml,${encodeURIComponent(svgContent)}`;\n };\n\n const updateDisplacementMap = () => {\n feImageRef.current?.setAttribute('href', generateDisplacementMap());\n };\n\n useEffect(() => {\n updateDisplacementMap();\n [\n { ref: redChannelRef, offset: redOffset },\n { ref: greenChannelRef, offset: greenOffset },\n { ref: blueChannelRef, offset: blueOffset }\n ].forEach(({ ref, offset }) => {\n if (ref.current) {\n ref.current.setAttribute('scale', (distortionScale + offset).toString());\n ref.current.setAttribute('xChannelSelector', xChannel);\n ref.current.setAttribute('yChannelSelector', yChannel);\n }\n });\n\n gaussianBlurRef.current?.setAttribute('stdDeviation', displace.toString());\n }, [\n width,\n height,\n borderRadius,\n borderWidth,\n brightness,\n opacity,\n blur,\n displace,\n distortionScale,\n redOffset,\n greenOffset,\n blueOffset,\n xChannel,\n yChannel,\n mixBlendMode\n ]);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const resizeObserver = new ResizeObserver(() => {\n setTimeout(updateDisplacementMap, 0);\n });\n\n resizeObserver.observe(containerRef.current);\n\n return () => {\n resizeObserver.disconnect();\n };\n }, []);\n\n useEffect(() => {\n setTimeout(updateDisplacementMap, 0);\n }, [width, height]);\n\n useEffect(() => {\n setSvgSupported(supportsSVGFilters());\n }, []);\n\n const supportsSVGFilters = () => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return false;\n }\n\n const isWebkit = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);\n const isFirefox = /Firefox/.test(navigator.userAgent);\n\n if (isWebkit || isFirefox) {\n return false;\n }\n\n const div = document.createElement('div');\n div.style.backdropFilter = `url(#${filterId})`;\n\n return div.style.backdropFilter !== '';\n };\n\n const containerStyle = {\n ...style,\n width: typeof width === 'number' ? `${width}px` : width,\n height: typeof height === 'number' ? `${height}px` : height,\n borderRadius: `${borderRadius}px`,\n '--glass-frost': backgroundOpacity,\n '--glass-saturation': saturation,\n '--filter-id': `url(#${filterId})`\n };\n\n return (\n \n \n \n \n \n\n \n \n\n \n \n\n \n \n\n \n \n \n \n \n \n\n
{children}
\n \n );\n};\n\nexport default GlassSurface;\n" } ], "registryDependencies": [], diff --git a/public/r/GlassSurface-JS-TW.json b/public/r/GlassSurface-JS-TW.json index f9aa138db..db8fa935a 100644 --- a/public/r/GlassSurface-JS-TW.json +++ b/public/r/GlassSurface-JS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "GlassSurface/GlassSurface.jsx", - "content": "/* eslint-disable react-hooks/exhaustive-deps */\nimport { useEffect, useRef, useState, useId } from 'react';\n\nconst useDarkMode = () => {\n const [isDark, setIsDark] = useState(false);\n\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');\n setIsDark(mediaQuery.matches);\n\n const handler = e => setIsDark(e.matches);\n mediaQuery.addEventListener('change', handler);\n return () => mediaQuery.removeEventListener('change', handler);\n }, []);\n\n return isDark;\n};\n\nconst GlassSurface = ({\n children,\n width = 200,\n height = 80,\n borderRadius = 20,\n borderWidth = 0.07,\n brightness = 50,\n opacity = 0.93,\n blur = 11,\n displace = 0,\n backgroundOpacity = 0,\n saturation = 1,\n distortionScale = -180,\n redOffset = 0,\n greenOffset = 10,\n blueOffset = 20,\n xChannel = 'R',\n yChannel = 'G',\n mixBlendMode = 'difference',\n className = '',\n style = {}\n}) => {\n const uniqueId = useId().replace(/:/g, '-');\n const filterId = `glass-filter-${uniqueId}`;\n const redGradId = `red-grad-${uniqueId}`;\n const blueGradId = `blue-grad-${uniqueId}`;\n\n const [svgSupported, setSvgSupported] = useState(false);\n\n const containerRef = useRef < HTMLDivElement > null;\n const feImageRef = useRef < SVGFEImageElement > null;\n const redChannelRef = useRef < SVGFEDisplacementMapElement > null;\n const greenChannelRef = useRef < SVGFEDisplacementMapElement > null;\n const blueChannelRef = useRef < SVGFEDisplacementMapElement > null;\n const gaussianBlurRef = useRef < SVGFEGaussianBlurElement > null;\n\n const isDarkMode = useDarkMode();\n\n const generateDisplacementMap = () => {\n const rect = containerRef.current?.getBoundingClientRect();\n const actualWidth = rect?.width || 400;\n const actualHeight = rect?.height || 200;\n const edgeSize = Math.min(actualWidth, actualHeight) * (borderWidth * 0.5);\n\n const svgContent = `\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n `;\n\n return `data:image/svg+xml,${encodeURIComponent(svgContent)}`;\n };\n\n const updateDisplacementMap = () => {\n feImageRef.current?.setAttribute('href', generateDisplacementMap());\n };\n\n useEffect(() => {\n updateDisplacementMap();\n [\n { ref: redChannelRef, offset: redOffset },\n { ref: greenChannelRef, offset: greenOffset },\n { ref: blueChannelRef, offset: blueOffset }\n ].forEach(({ ref, offset }) => {\n if (ref.current) {\n ref.current.setAttribute('scale', (distortionScale + offset).toString());\n ref.current.setAttribute('xChannelSelector', xChannel);\n ref.current.setAttribute('yChannelSelector', yChannel);\n }\n });\n\n gaussianBlurRef.current?.setAttribute('stdDeviation', displace.toString());\n }, [\n width,\n height,\n borderRadius,\n borderWidth,\n brightness,\n opacity,\n blur,\n displace,\n distortionScale,\n redOffset,\n greenOffset,\n blueOffset,\n xChannel,\n yChannel,\n mixBlendMode\n ]);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const resizeObserver = new ResizeObserver(() => {\n setTimeout(updateDisplacementMap, 0);\n });\n\n resizeObserver.observe(containerRef.current);\n\n return () => {\n resizeObserver.disconnect();\n };\n }, []);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const resizeObserver = new ResizeObserver(() => {\n setTimeout(updateDisplacementMap, 0);\n });\n\n resizeObserver.observe(containerRef.current);\n\n return () => {\n resizeObserver.disconnect();\n };\n }, []);\n\n useEffect(() => {\n setTimeout(updateDisplacementMap, 0);\n }, [width, height]);\n\n useEffect(() => {\n setSvgSupported(supportsSVGFilters());\n }, []);\n\n const supportsSVGFilters = () => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return false;\n }\n\n const isWebkit = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);\n const isFirefox = /Firefox/.test(navigator.userAgent);\n\n if (isWebkit || isFirefox) {\n return false;\n }\n\n const div = document.createElement('div');\n div.style.backdropFilter = `url(#${filterId})`;\n\n return div.style.backdropFilter !== '';\n };\n\n const supportsBackdropFilter = () => {\n if (typeof window === 'undefined') return false;\n return CSS.supports('backdrop-filter', 'blur(10px)');\n };\n\n const getContainerStyles = () => {\n const baseStyles = {\n ...style,\n width: typeof width === 'number' ? `${width}px` : width,\n height: typeof height === 'number' ? `${height}px` : height,\n borderRadius: `${borderRadius}px`,\n '--glass-frost': backgroundOpacity,\n '--glass-saturation': saturation\n };\n\n const backdropFilterSupported = supportsBackdropFilter();\n\n if (svgSupported) {\n return {\n ...baseStyles,\n background: isDarkMode ? `hsl(0 0% 0% / ${backgroundOpacity})` : `hsl(0 0% 100% / ${backgroundOpacity})`,\n backdropFilter: `url(#${filterId}) saturate(${saturation})`,\n boxShadow: isDarkMode\n ? `0 0 2px 1px color-mix(in oklch, white, transparent 65%) inset,\n 0 0 10px 4px color-mix(in oklch, white, transparent 85%) inset,\n 0px 4px 16px rgba(17, 17, 26, 0.05),\n 0px 8px 24px rgba(17, 17, 26, 0.05),\n 0px 16px 56px rgba(17, 17, 26, 0.05),\n 0px 4px 16px rgba(17, 17, 26, 0.05) inset,\n 0px 8px 24px rgba(17, 17, 26, 0.05) inset,\n 0px 16px 56px rgba(17, 17, 26, 0.05) inset`\n : `0 0 2px 1px color-mix(in oklch, black, transparent 85%) inset,\n 0 0 10px 4px color-mix(in oklch, black, transparent 90%) inset,\n 0px 4px 16px rgba(17, 17, 26, 0.05),\n 0px 8px 24px rgba(17, 17, 26, 0.05),\n 0px 16px 56px rgba(17, 17, 26, 0.05),\n 0px 4px 16px rgba(17, 17, 26, 0.05) inset,\n 0px 8px 24px rgba(17, 17, 26, 0.05) inset,\n 0px 16px 56px rgba(17, 17, 26, 0.05) inset`\n };\n } else {\n if (isDarkMode) {\n if (!backdropFilterSupported) {\n return {\n ...baseStyles,\n background: 'rgba(0, 0, 0, 0.4)',\n border: '1px solid rgba(255, 255, 255, 0.2)',\n boxShadow: `inset 0 1px 0 0 rgba(255, 255, 255, 0.2),\n inset 0 -1px 0 0 rgba(255, 255, 255, 0.1)`\n };\n } else {\n return {\n ...baseStyles,\n background: 'rgba(255, 255, 255, 0.1)',\n backdropFilter: 'blur(12px) saturate(1.8) brightness(1.2)',\n WebkitBackdropFilter: 'blur(12px) saturate(1.8) brightness(1.2)',\n border: '1px solid rgba(255, 255, 255, 0.2)',\n boxShadow: `inset 0 1px 0 0 rgba(255, 255, 255, 0.2),\n inset 0 -1px 0 0 rgba(255, 255, 255, 0.1)`\n };\n }\n } else {\n if (!backdropFilterSupported) {\n return {\n ...baseStyles,\n background: 'rgba(255, 255, 255, 0.4)',\n border: '1px solid rgba(255, 255, 255, 0.3)',\n boxShadow: `inset 0 1px 0 0 rgba(255, 255, 255, 0.5),\n inset 0 -1px 0 0 rgba(255, 255, 255, 0.3)`\n };\n } else {\n return {\n ...baseStyles,\n background: 'rgba(255, 255, 255, 0.25)',\n backdropFilter: 'blur(12px) saturate(1.8) brightness(1.1)',\n WebkitBackdropFilter: 'blur(12px) saturate(1.8) brightness(1.1)',\n border: '1px solid rgba(255, 255, 255, 0.3)',\n boxShadow: `0 8px 32px 0 rgba(31, 38, 135, 0.2),\n 0 2px 16px 0 rgba(31, 38, 135, 0.1),\n inset 0 1px 0 0 rgba(255, 255, 255, 0.4),\n inset 0 -1px 0 0 rgba(255, 255, 255, 0.2)`\n };\n }\n }\n }\n };\n\n const glassSurfaceClasses =\n 'relative flex items-center justify-center overflow-hidden transition-opacity duration-[260ms] ease-out';\n\n const focusVisibleClasses = isDarkMode\n ? 'focus-visible:outline-2 focus-visible:outline-[#0A84FF] focus-visible:outline-offset-2'\n : 'focus-visible:outline-2 focus-visible:outline-[#007AFF] focus-visible:outline-offset-2';\n\n return (\n \n \n \n \n \n\n \n \n\n \n \n\n \n \n\n \n \n \n \n \n \n\n
\n {children}\n
\n \n );\n};\n\nexport default GlassSurface;\n" + "content": "/* eslint-disable react-hooks/exhaustive-deps */\nimport { useEffect, useRef, useState, useId } from 'react';\n\nconst useDarkMode = () => {\n const [isDark, setIsDark] = useState(false);\n\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');\n setIsDark(mediaQuery.matches);\n\n const handler = e => setIsDark(e.matches);\n mediaQuery.addEventListener('change', handler);\n return () => mediaQuery.removeEventListener('change', handler);\n }, []);\n\n return isDark;\n};\n\nconst GlassSurface = ({\n children,\n width = 200,\n height = 80,\n borderRadius = 20,\n borderWidth = 0.07,\n brightness = 50,\n opacity = 0.93,\n blur = 11,\n displace = 0,\n backgroundOpacity = 0,\n saturation = 1,\n distortionScale = -180,\n redOffset = 0,\n greenOffset = 10,\n blueOffset = 20,\n xChannel = 'R',\n yChannel = 'G',\n mixBlendMode = 'difference',\n className = '',\n style = {}\n}) => {\n const uniqueId = useId().replace(/:/g, '-');\n const filterId = `glass-filter-${uniqueId}`;\n const redGradId = `red-grad-${uniqueId}`;\n const blueGradId = `blue-grad-${uniqueId}`;\n\n const [svgSupported, setSvgSupported] = useState(false);\n\n const containerRef = useRef < HTMLDivElement > null;\n const feImageRef = useRef < SVGFEImageElement > null;\n const redChannelRef = useRef < SVGFEDisplacementMapElement > null;\n const greenChannelRef = useRef < SVGFEDisplacementMapElement > null;\n const blueChannelRef = useRef < SVGFEDisplacementMapElement > null;\n const gaussianBlurRef = useRef < SVGFEGaussianBlurElement > null;\n\n const isDarkMode = useDarkMode();\n\n const generateDisplacementMap = () => {\n const rect = containerRef.current?.getBoundingClientRect();\n const actualWidth = rect?.width || 400;\n const actualHeight = rect?.height || 200;\n const edgeSize = Math.min(actualWidth, actualHeight) * (borderWidth * 0.5);\n\n const svgContent = `\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n `;\n\n return `data:image/svg+xml,${encodeURIComponent(svgContent)}`;\n };\n\n const updateDisplacementMap = () => {\n feImageRef.current?.setAttribute('href', generateDisplacementMap());\n };\n\n useEffect(() => {\n updateDisplacementMap();\n [\n { ref: redChannelRef, offset: redOffset },\n { ref: greenChannelRef, offset: greenOffset },\n { ref: blueChannelRef, offset: blueOffset }\n ].forEach(({ ref, offset }) => {\n if (ref.current) {\n ref.current.setAttribute('scale', (distortionScale + offset).toString());\n ref.current.setAttribute('xChannelSelector', xChannel);\n ref.current.setAttribute('yChannelSelector', yChannel);\n }\n });\n\n gaussianBlurRef.current?.setAttribute('stdDeviation', displace.toString());\n }, [\n width,\n height,\n borderRadius,\n borderWidth,\n brightness,\n opacity,\n blur,\n displace,\n distortionScale,\n redOffset,\n greenOffset,\n blueOffset,\n xChannel,\n yChannel,\n mixBlendMode\n ]);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const resizeObserver = new ResizeObserver(() => {\n setTimeout(updateDisplacementMap, 0);\n });\n\n resizeObserver.observe(containerRef.current);\n\n return () => {\n resizeObserver.disconnect();\n };\n }, []);\n\n useEffect(() => {\n setTimeout(updateDisplacementMap, 0);\n }, [width, height]);\n\n useEffect(() => {\n setSvgSupported(supportsSVGFilters());\n }, []);\n\n const supportsSVGFilters = () => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return false;\n }\n\n const isWebkit = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);\n const isFirefox = /Firefox/.test(navigator.userAgent);\n\n if (isWebkit || isFirefox) {\n return false;\n }\n\n const div = document.createElement('div');\n div.style.backdropFilter = `url(#${filterId})`;\n\n return div.style.backdropFilter !== '';\n };\n\n const supportsBackdropFilter = () => {\n if (typeof window === 'undefined') return false;\n return CSS.supports('backdrop-filter', 'blur(10px)');\n };\n\n const getContainerStyles = () => {\n const baseStyles = {\n ...style,\n width: typeof width === 'number' ? `${width}px` : width,\n height: typeof height === 'number' ? `${height}px` : height,\n borderRadius: `${borderRadius}px`,\n '--glass-frost': backgroundOpacity,\n '--glass-saturation': saturation\n };\n\n const backdropFilterSupported = supportsBackdropFilter();\n\n if (svgSupported) {\n return {\n ...baseStyles,\n background: isDarkMode ? `hsl(0 0% 0% / ${backgroundOpacity})` : `hsl(0 0% 100% / ${backgroundOpacity})`,\n backdropFilter: `url(#${filterId}) saturate(${saturation})`,\n boxShadow: isDarkMode\n ? `0 0 2px 1px color-mix(in oklch, white, transparent 65%) inset,\n 0 0 10px 4px color-mix(in oklch, white, transparent 85%) inset,\n 0px 4px 16px rgba(17, 17, 26, 0.05),\n 0px 8px 24px rgba(17, 17, 26, 0.05),\n 0px 16px 56px rgba(17, 17, 26, 0.05),\n 0px 4px 16px rgba(17, 17, 26, 0.05) inset,\n 0px 8px 24px rgba(17, 17, 26, 0.05) inset,\n 0px 16px 56px rgba(17, 17, 26, 0.05) inset`\n : `0 0 2px 1px color-mix(in oklch, black, transparent 85%) inset,\n 0 0 10px 4px color-mix(in oklch, black, transparent 90%) inset,\n 0px 4px 16px rgba(17, 17, 26, 0.05),\n 0px 8px 24px rgba(17, 17, 26, 0.05),\n 0px 16px 56px rgba(17, 17, 26, 0.05),\n 0px 4px 16px rgba(17, 17, 26, 0.05) inset,\n 0px 8px 24px rgba(17, 17, 26, 0.05) inset,\n 0px 16px 56px rgba(17, 17, 26, 0.05) inset`\n };\n } else {\n if (isDarkMode) {\n if (!backdropFilterSupported) {\n return {\n ...baseStyles,\n background: 'rgba(0, 0, 0, 0.4)',\n border: '1px solid rgba(255, 255, 255, 0.2)',\n boxShadow: `inset 0 1px 0 0 rgba(255, 255, 255, 0.2),\n inset 0 -1px 0 0 rgba(255, 255, 255, 0.1)`\n };\n } else {\n return {\n ...baseStyles,\n background: 'rgba(255, 255, 255, 0.1)',\n backdropFilter: 'blur(12px) saturate(1.8) brightness(1.2)',\n WebkitBackdropFilter: 'blur(12px) saturate(1.8) brightness(1.2)',\n border: '1px solid rgba(255, 255, 255, 0.2)',\n boxShadow: `inset 0 1px 0 0 rgba(255, 255, 255, 0.2),\n inset 0 -1px 0 0 rgba(255, 255, 255, 0.1)`\n };\n }\n } else {\n if (!backdropFilterSupported) {\n return {\n ...baseStyles,\n background: 'rgba(255, 255, 255, 0.4)',\n border: '1px solid rgba(255, 255, 255, 0.3)',\n boxShadow: `inset 0 1px 0 0 rgba(255, 255, 255, 0.5),\n inset 0 -1px 0 0 rgba(255, 255, 255, 0.3)`\n };\n } else {\n return {\n ...baseStyles,\n background: 'rgba(255, 255, 255, 0.25)',\n backdropFilter: 'blur(12px) saturate(1.8) brightness(1.1)',\n WebkitBackdropFilter: 'blur(12px) saturate(1.8) brightness(1.1)',\n border: '1px solid rgba(255, 255, 255, 0.3)',\n boxShadow: `0 8px 32px 0 rgba(31, 38, 135, 0.2),\n 0 2px 16px 0 rgba(31, 38, 135, 0.1),\n inset 0 1px 0 0 rgba(255, 255, 255, 0.4),\n inset 0 -1px 0 0 rgba(255, 255, 255, 0.2)`\n };\n }\n }\n }\n };\n\n const glassSurfaceClasses =\n 'relative flex items-center justify-center overflow-hidden transition-opacity duration-[260ms] ease-out';\n\n const focusVisibleClasses = isDarkMode\n ? 'focus-visible:outline-2 focus-visible:outline-[#0A84FF] focus-visible:outline-offset-2'\n : 'focus-visible:outline-2 focus-visible:outline-[#007AFF] focus-visible:outline-offset-2';\n\n return (\n \n \n \n \n \n\n \n \n\n \n \n\n \n \n\n \n \n \n \n \n \n\n
\n {children}\n
\n \n );\n};\n\nexport default GlassSurface;\n" } ], "registryDependencies": [], diff --git a/public/r/GlassSurface-TS-CSS.json b/public/r/GlassSurface-TS-CSS.json index ea2faeb8b..c67acfc93 100644 --- a/public/r/GlassSurface-TS-CSS.json +++ b/public/r/GlassSurface-TS-CSS.json @@ -13,7 +13,7 @@ { "type": "registry:component", "path": "GlassSurface/GlassSurface.tsx", - "content": "import React, { useEffect, useRef, useState, useId } from 'react';\nimport './GlassSurface.css';\n\nexport interface GlassSurfaceProps {\n children?: React.ReactNode;\n width?: number | string;\n height?: number | string;\n borderRadius?: number;\n borderWidth?: number;\n brightness?: number;\n opacity?: number;\n blur?: number;\n displace?: number;\n backgroundOpacity?: number;\n saturation?: number;\n distortionScale?: number;\n redOffset?: number;\n greenOffset?: number;\n blueOffset?: number;\n xChannel?: 'R' | 'G' | 'B';\n yChannel?: 'R' | 'G' | 'B';\n mixBlendMode?:\n | 'normal'\n | 'multiply'\n | 'screen'\n | 'overlay'\n | 'darken'\n | 'lighten'\n | 'color-dodge'\n | 'color-burn'\n | 'hard-light'\n | 'soft-light'\n | 'difference'\n | 'exclusion'\n | 'hue'\n | 'saturation'\n | 'color'\n | 'luminosity'\n | 'plus-darker'\n | 'plus-lighter';\n className?: string;\n style?: React.CSSProperties;\n}\n\nconst GlassSurface: React.FC = ({\n children,\n width = 200,\n height = 80,\n borderRadius = 20,\n borderWidth = 0.07,\n brightness = 50,\n opacity = 0.93,\n blur = 11,\n displace = 0,\n backgroundOpacity = 0,\n saturation = 1,\n distortionScale = -180,\n redOffset = 0,\n greenOffset = 10,\n blueOffset = 20,\n xChannel = 'R',\n yChannel = 'G',\n mixBlendMode = 'difference',\n className = '',\n style = {}\n}) => {\n const id = useId();\n const filterId = `glass-filter-${id}`;\n const redGradId = `red-grad-${id}`;\n const blueGradId = `blue-grad-${id}`;\n\n const [svgSupported, setSvgSupported] = useState(false);\n\n const containerRef = useRef(null);\n const feImageRef = useRef(null);\n const redChannelRef = useRef(null);\n const greenChannelRef = useRef(null);\n const blueChannelRef = useRef(null);\n const gaussianBlurRef = useRef(null);\n\n const generateDisplacementMap = () => {\n const rect = containerRef.current?.getBoundingClientRect();\n const actualWidth = rect?.width || 400;\n const actualHeight = rect?.height || 200;\n const edgeSize = Math.min(actualWidth, actualHeight) * (borderWidth * 0.5);\n\n const svgContent = `\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n `;\n\n return `data:image/svg+xml,${encodeURIComponent(svgContent)}`;\n };\n\n const updateDisplacementMap = () => {\n feImageRef.current?.setAttribute('href', generateDisplacementMap());\n };\n\n useEffect(() => {\n updateDisplacementMap();\n [\n { ref: redChannelRef, offset: redOffset },\n { ref: greenChannelRef, offset: greenOffset },\n { ref: blueChannelRef, offset: blueOffset }\n ].forEach(({ ref, offset }) => {\n if (ref.current) {\n ref.current.setAttribute('scale', (distortionScale + offset).toString());\n ref.current.setAttribute('xChannelSelector', xChannel);\n ref.current.setAttribute('yChannelSelector', yChannel);\n }\n });\n\n gaussianBlurRef.current?.setAttribute('stdDeviation', displace.toString());\n }, [\n width,\n height,\n borderRadius,\n borderWidth,\n brightness,\n opacity,\n blur,\n displace,\n distortionScale,\n redOffset,\n greenOffset,\n blueOffset,\n xChannel,\n yChannel,\n mixBlendMode\n ]);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const resizeObserver = new ResizeObserver(() => {\n setTimeout(updateDisplacementMap, 0);\n });\n\n resizeObserver.observe(containerRef.current);\n\n return () => {\n resizeObserver.disconnect();\n };\n }, []);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const resizeObserver = new ResizeObserver(() => {\n setTimeout(updateDisplacementMap, 0);\n });\n\n resizeObserver.observe(containerRef.current);\n\n return () => {\n resizeObserver.disconnect();\n };\n }, []);\n\n useEffect(() => {\n setTimeout(updateDisplacementMap, 0);\n }, [width, height]);\n\n useEffect(() => {\n setSvgSupported(supportsSVGFilters());\n }, []);\n\n const supportsSVGFilters = () => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return false;\n }\n\n const isWebkit = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);\n const isFirefox = /Firefox/.test(navigator.userAgent);\n\n if (isWebkit || isFirefox) {\n return false;\n }\n\n const div = document.createElement('div');\n div.style.backdropFilter = `url(#${filterId})`;\n\n return div.style.backdropFilter !== '';\n };\n\n const containerStyle: React.CSSProperties = {\n ...style,\n width: typeof width === 'number' ? `${width}px` : width,\n height: typeof height === 'number' ? `${height}px` : height,\n borderRadius: `${borderRadius}px`,\n '--glass-frost': backgroundOpacity,\n '--glass-saturation': saturation,\n '--filter-id': `url(#${filterId})`\n } as React.CSSProperties;\n\n return (\n \n \n \n \n \n\n \n \n\n \n \n\n \n \n\n \n \n \n \n \n \n\n
{children}
\n \n );\n};\n\nexport default GlassSurface;\n" + "content": "import React, { useEffect, useRef, useState, useId } from 'react';\nimport './GlassSurface.css';\n\nexport interface GlassSurfaceProps {\n children?: React.ReactNode;\n width?: number | string;\n height?: number | string;\n borderRadius?: number;\n borderWidth?: number;\n brightness?: number;\n opacity?: number;\n blur?: number;\n displace?: number;\n backgroundOpacity?: number;\n saturation?: number;\n distortionScale?: number;\n redOffset?: number;\n greenOffset?: number;\n blueOffset?: number;\n xChannel?: 'R' | 'G' | 'B';\n yChannel?: 'R' | 'G' | 'B';\n mixBlendMode?:\n | 'normal'\n | 'multiply'\n | 'screen'\n | 'overlay'\n | 'darken'\n | 'lighten'\n | 'color-dodge'\n | 'color-burn'\n | 'hard-light'\n | 'soft-light'\n | 'difference'\n | 'exclusion'\n | 'hue'\n | 'saturation'\n | 'color'\n | 'luminosity'\n | 'plus-darker'\n | 'plus-lighter';\n className?: string;\n style?: React.CSSProperties;\n}\n\nconst GlassSurface: React.FC = ({\n children,\n width = 200,\n height = 80,\n borderRadius = 20,\n borderWidth = 0.07,\n brightness = 50,\n opacity = 0.93,\n blur = 11,\n displace = 0,\n backgroundOpacity = 0,\n saturation = 1,\n distortionScale = -180,\n redOffset = 0,\n greenOffset = 10,\n blueOffset = 20,\n xChannel = 'R',\n yChannel = 'G',\n mixBlendMode = 'difference',\n className = '',\n style = {}\n}) => {\n const id = useId();\n const filterId = `glass-filter-${id}`;\n const redGradId = `red-grad-${id}`;\n const blueGradId = `blue-grad-${id}`;\n\n const [svgSupported, setSvgSupported] = useState(false);\n\n const containerRef = useRef(null);\n const feImageRef = useRef(null);\n const redChannelRef = useRef(null);\n const greenChannelRef = useRef(null);\n const blueChannelRef = useRef(null);\n const gaussianBlurRef = useRef(null);\n\n const generateDisplacementMap = () => {\n const rect = containerRef.current?.getBoundingClientRect();\n const actualWidth = rect?.width || 400;\n const actualHeight = rect?.height || 200;\n const edgeSize = Math.min(actualWidth, actualHeight) * (borderWidth * 0.5);\n\n const svgContent = `\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n `;\n\n return `data:image/svg+xml,${encodeURIComponent(svgContent)}`;\n };\n\n const updateDisplacementMap = () => {\n feImageRef.current?.setAttribute('href', generateDisplacementMap());\n };\n\n useEffect(() => {\n updateDisplacementMap();\n [\n { ref: redChannelRef, offset: redOffset },\n { ref: greenChannelRef, offset: greenOffset },\n { ref: blueChannelRef, offset: blueOffset }\n ].forEach(({ ref, offset }) => {\n if (ref.current) {\n ref.current.setAttribute('scale', (distortionScale + offset).toString());\n ref.current.setAttribute('xChannelSelector', xChannel);\n ref.current.setAttribute('yChannelSelector', yChannel);\n }\n });\n\n gaussianBlurRef.current?.setAttribute('stdDeviation', displace.toString());\n }, [\n width,\n height,\n borderRadius,\n borderWidth,\n brightness,\n opacity,\n blur,\n displace,\n distortionScale,\n redOffset,\n greenOffset,\n blueOffset,\n xChannel,\n yChannel,\n mixBlendMode\n ]);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const resizeObserver = new ResizeObserver(() => {\n setTimeout(updateDisplacementMap, 0);\n });\n\n resizeObserver.observe(containerRef.current);\n\n return () => {\n resizeObserver.disconnect();\n };\n }, []);\n\n useEffect(() => {\n setTimeout(updateDisplacementMap, 0);\n }, [width, height]);\n\n useEffect(() => {\n setSvgSupported(supportsSVGFilters());\n }, []);\n\n const supportsSVGFilters = () => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return false;\n }\n\n const isWebkit = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);\n const isFirefox = /Firefox/.test(navigator.userAgent);\n\n if (isWebkit || isFirefox) {\n return false;\n }\n\n const div = document.createElement('div');\n div.style.backdropFilter = `url(#${filterId})`;\n\n return div.style.backdropFilter !== '';\n };\n\n const containerStyle: React.CSSProperties = {\n ...style,\n width: typeof width === 'number' ? `${width}px` : width,\n height: typeof height === 'number' ? `${height}px` : height,\n borderRadius: `${borderRadius}px`,\n '--glass-frost': backgroundOpacity,\n '--glass-saturation': saturation,\n '--filter-id': `url(#${filterId})`\n } as React.CSSProperties;\n\n return (\n \n \n \n \n \n\n \n \n\n \n \n\n \n \n\n \n \n \n \n \n \n\n
{children}
\n \n );\n};\n\nexport default GlassSurface;\n" } ], "registryDependencies": [], diff --git a/public/r/GlassSurface-TS-TW.json b/public/r/GlassSurface-TS-TW.json index 1a8a3f882..338bc422b 100644 --- a/public/r/GlassSurface-TS-TW.json +++ b/public/r/GlassSurface-TS-TW.json @@ -8,7 +8,7 @@ { "type": "registry:component", "path": "GlassSurface/GlassSurface.tsx", - "content": "import React, { useEffect, useRef, useState, useId } from 'react';\n\nexport interface GlassSurfaceProps {\n children?: React.ReactNode;\n width?: number | string;\n height?: number | string;\n borderRadius?: number;\n borderWidth?: number;\n brightness?: number;\n opacity?: number;\n blur?: number;\n displace?: number;\n backgroundOpacity?: number;\n saturation?: number;\n distortionScale?: number;\n redOffset?: number;\n greenOffset?: number;\n blueOffset?: number;\n xChannel?: 'R' | 'G' | 'B';\n yChannel?: 'R' | 'G' | 'B';\n mixBlendMode?:\n | 'normal'\n | 'multiply'\n | 'screen'\n | 'overlay'\n | 'darken'\n | 'lighten'\n | 'color-dodge'\n | 'color-burn'\n | 'hard-light'\n | 'soft-light'\n | 'difference'\n | 'exclusion'\n | 'hue'\n | 'saturation'\n | 'color'\n | 'luminosity'\n | 'plus-darker'\n | 'plus-lighter';\n className?: string;\n style?: React.CSSProperties;\n}\n\nconst useDarkMode = () => {\n const [isDark, setIsDark] = useState(false);\n\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');\n setIsDark(mediaQuery.matches);\n\n const handler = (e: MediaQueryListEvent) => setIsDark(e.matches);\n mediaQuery.addEventListener('change', handler);\n return () => mediaQuery.removeEventListener('change', handler);\n }, []);\n\n return isDark;\n};\n\nconst GlassSurface: React.FC = ({\n children,\n width = 200,\n height = 80,\n borderRadius = 20,\n borderWidth = 0.07,\n brightness = 50,\n opacity = 0.93,\n blur = 11,\n displace = 0,\n backgroundOpacity = 0,\n saturation = 1,\n distortionScale = -180,\n redOffset = 0,\n greenOffset = 10,\n blueOffset = 20,\n xChannel = 'R',\n yChannel = 'G',\n mixBlendMode = 'difference',\n className = '',\n style = {}\n}) => {\n const uniqueId = useId().replace(/:/g, '-');\n const filterId = `glass-filter-${uniqueId}`;\n const redGradId = `red-grad-${uniqueId}`;\n const blueGradId = `blue-grad-${uniqueId}`;\n\n const [svgSupported, setSvgSupported] = useState(false);\n\n const containerRef = useRef(null);\n const feImageRef = useRef(null);\n const redChannelRef = useRef(null);\n const greenChannelRef = useRef(null);\n const blueChannelRef = useRef(null);\n const gaussianBlurRef = useRef(null);\n\n const isDarkMode = useDarkMode();\n\n const generateDisplacementMap = () => {\n const rect = containerRef.current?.getBoundingClientRect();\n const actualWidth = rect?.width || 400;\n const actualHeight = rect?.height || 200;\n const edgeSize = Math.min(actualWidth, actualHeight) * (borderWidth * 0.5);\n\n const svgContent = `\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n `;\n\n return `data:image/svg+xml,${encodeURIComponent(svgContent)}`;\n };\n\n const updateDisplacementMap = () => {\n feImageRef.current?.setAttribute('href', generateDisplacementMap());\n };\n\n useEffect(() => {\n updateDisplacementMap();\n [\n { ref: redChannelRef, offset: redOffset },\n { ref: greenChannelRef, offset: greenOffset },\n { ref: blueChannelRef, offset: blueOffset }\n ].forEach(({ ref, offset }) => {\n if (ref.current) {\n ref.current.setAttribute('scale', (distortionScale + offset).toString());\n ref.current.setAttribute('xChannelSelector', xChannel);\n ref.current.setAttribute('yChannelSelector', yChannel);\n }\n });\n\n gaussianBlurRef.current?.setAttribute('stdDeviation', displace.toString());\n }, [\n width,\n height,\n borderRadius,\n borderWidth,\n brightness,\n opacity,\n blur,\n displace,\n distortionScale,\n redOffset,\n greenOffset,\n blueOffset,\n xChannel,\n yChannel,\n mixBlendMode\n ]);\n\n useEffect(() => {\n setSvgSupported(supportsSVGFilters());\n }, []);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const resizeObserver = new ResizeObserver(() => {\n setTimeout(updateDisplacementMap, 0);\n });\n\n resizeObserver.observe(containerRef.current);\n\n return () => {\n resizeObserver.disconnect();\n };\n }, []);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const resizeObserver = new ResizeObserver(() => {\n setTimeout(updateDisplacementMap, 0);\n });\n\n resizeObserver.observe(containerRef.current);\n\n return () => {\n resizeObserver.disconnect();\n };\n }, []);\n\n useEffect(() => {\n setTimeout(updateDisplacementMap, 0);\n }, [width, height]);\n\n const supportsSVGFilters = () => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return false;\n }\n\n const isWebkit = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);\n const isFirefox = /Firefox/.test(navigator.userAgent);\n\n if (isWebkit || isFirefox) {\n return false;\n }\n\n const div = document.createElement('div');\n div.style.backdropFilter = `url(#${filterId})`;\n\n return div.style.backdropFilter !== '';\n };\n\n const supportsBackdropFilter = () => {\n if (typeof window === 'undefined') return false;\n return CSS.supports('backdrop-filter', 'blur(10px)');\n };\n\n const getContainerStyles = (): React.CSSProperties => {\n const baseStyles: React.CSSProperties = {\n ...style,\n width: typeof width === 'number' ? `${width}px` : width,\n height: typeof height === 'number' ? `${height}px` : height,\n borderRadius: `${borderRadius}px`,\n '--glass-frost': backgroundOpacity,\n '--glass-saturation': saturation\n } as React.CSSProperties;\n\n const backdropFilterSupported = supportsBackdropFilter();\n\n if (svgSupported) {\n return {\n ...baseStyles,\n background: isDarkMode ? `hsl(0 0% 0% / ${backgroundOpacity})` : `hsl(0 0% 100% / ${backgroundOpacity})`,\n backdropFilter: `url(#${filterId}) saturate(${saturation})`,\n boxShadow: isDarkMode\n ? `0 0 2px 1px color-mix(in oklch, white, transparent 65%) inset,\n 0 0 10px 4px color-mix(in oklch, white, transparent 85%) inset,\n 0px 4px 16px rgba(17, 17, 26, 0.05),\n 0px 8px 24px rgba(17, 17, 26, 0.05),\n 0px 16px 56px rgba(17, 17, 26, 0.05),\n 0px 4px 16px rgba(17, 17, 26, 0.05) inset,\n 0px 8px 24px rgba(17, 17, 26, 0.05) inset,\n 0px 16px 56px rgba(17, 17, 26, 0.05) inset`\n : `0 0 2px 1px color-mix(in oklch, black, transparent 85%) inset,\n 0 0 10px 4px color-mix(in oklch, black, transparent 90%) inset,\n 0px 4px 16px rgba(17, 17, 26, 0.05),\n 0px 8px 24px rgba(17, 17, 26, 0.05),\n 0px 16px 56px rgba(17, 17, 26, 0.05),\n 0px 4px 16px rgba(17, 17, 26, 0.05) inset,\n 0px 8px 24px rgba(17, 17, 26, 0.05) inset,\n 0px 16px 56px rgba(17, 17, 26, 0.05) inset`\n };\n } else {\n if (isDarkMode) {\n if (!backdropFilterSupported) {\n return {\n ...baseStyles,\n background: 'rgba(0, 0, 0, 0.4)',\n border: '1px solid rgba(255, 255, 255, 0.2)',\n boxShadow: `inset 0 1px 0 0 rgba(255, 255, 255, 0.2),\n inset 0 -1px 0 0 rgba(255, 255, 255, 0.1)`\n };\n } else {\n return {\n ...baseStyles,\n background: 'rgba(255, 255, 255, 0.1)',\n backdropFilter: 'blur(12px) saturate(1.8) brightness(1.2)',\n WebkitBackdropFilter: 'blur(12px) saturate(1.8) brightness(1.2)',\n border: '1px solid rgba(255, 255, 255, 0.2)',\n boxShadow: `inset 0 1px 0 0 rgba(255, 255, 255, 0.2),\n inset 0 -1px 0 0 rgba(255, 255, 255, 0.1)`\n };\n }\n } else {\n if (!backdropFilterSupported) {\n return {\n ...baseStyles,\n background: 'rgba(255, 255, 255, 0.4)',\n border: '1px solid rgba(255, 255, 255, 0.3)',\n boxShadow: `inset 0 1px 0 0 rgba(255, 255, 255, 0.5),\n inset 0 -1px 0 0 rgba(255, 255, 255, 0.3)`\n };\n } else {\n return {\n ...baseStyles,\n background: 'rgba(255, 255, 255, 0.25)',\n backdropFilter: 'blur(12px) saturate(1.8) brightness(1.1)',\n WebkitBackdropFilter: 'blur(12px) saturate(1.8) brightness(1.1)',\n border: '1px solid rgba(255, 255, 255, 0.3)',\n boxShadow: `0 8px 32px 0 rgba(31, 38, 135, 0.2),\n 0 2px 16px 0 rgba(31, 38, 135, 0.1),\n inset 0 1px 0 0 rgba(255, 255, 255, 0.4),\n inset 0 -1px 0 0 rgba(255, 255, 255, 0.2)`\n };\n }\n }\n }\n };\n\n const glassSurfaceClasses =\n 'relative flex items-center justify-center overflow-hidden transition-opacity duration-[260ms] ease-out';\n\n const focusVisibleClasses = isDarkMode\n ? 'focus-visible:outline-2 focus-visible:outline-[#0A84FF] focus-visible:outline-offset-2'\n : 'focus-visible:outline-2 focus-visible:outline-[#007AFF] focus-visible:outline-offset-2';\n\n return (\n \n \n \n \n \n\n \n \n\n \n \n\n \n \n\n \n \n \n \n \n \n\n
\n {children}\n
\n \n );\n};\n\nexport default GlassSurface;\n" + "content": "import React, { useEffect, useRef, useState, useId } from 'react';\n\nexport interface GlassSurfaceProps {\n children?: React.ReactNode;\n width?: number | string;\n height?: number | string;\n borderRadius?: number;\n borderWidth?: number;\n brightness?: number;\n opacity?: number;\n blur?: number;\n displace?: number;\n backgroundOpacity?: number;\n saturation?: number;\n distortionScale?: number;\n redOffset?: number;\n greenOffset?: number;\n blueOffset?: number;\n xChannel?: 'R' | 'G' | 'B';\n yChannel?: 'R' | 'G' | 'B';\n mixBlendMode?:\n | 'normal'\n | 'multiply'\n | 'screen'\n | 'overlay'\n | 'darken'\n | 'lighten'\n | 'color-dodge'\n | 'color-burn'\n | 'hard-light'\n | 'soft-light'\n | 'difference'\n | 'exclusion'\n | 'hue'\n | 'saturation'\n | 'color'\n | 'luminosity'\n | 'plus-darker'\n | 'plus-lighter';\n className?: string;\n style?: React.CSSProperties;\n}\n\nconst useDarkMode = () => {\n const [isDark, setIsDark] = useState(false);\n\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');\n setIsDark(mediaQuery.matches);\n\n const handler = (e: MediaQueryListEvent) => setIsDark(e.matches);\n mediaQuery.addEventListener('change', handler);\n return () => mediaQuery.removeEventListener('change', handler);\n }, []);\n\n return isDark;\n};\n\nconst GlassSurface: React.FC = ({\n children,\n width = 200,\n height = 80,\n borderRadius = 20,\n borderWidth = 0.07,\n brightness = 50,\n opacity = 0.93,\n blur = 11,\n displace = 0,\n backgroundOpacity = 0,\n saturation = 1,\n distortionScale = -180,\n redOffset = 0,\n greenOffset = 10,\n blueOffset = 20,\n xChannel = 'R',\n yChannel = 'G',\n mixBlendMode = 'difference',\n className = '',\n style = {}\n}) => {\n const uniqueId = useId().replace(/:/g, '-');\n const filterId = `glass-filter-${uniqueId}`;\n const redGradId = `red-grad-${uniqueId}`;\n const blueGradId = `blue-grad-${uniqueId}`;\n\n const [svgSupported, setSvgSupported] = useState(false);\n\n const containerRef = useRef(null);\n const feImageRef = useRef(null);\n const redChannelRef = useRef(null);\n const greenChannelRef = useRef(null);\n const blueChannelRef = useRef(null);\n const gaussianBlurRef = useRef(null);\n\n const isDarkMode = useDarkMode();\n\n const generateDisplacementMap = () => {\n const rect = containerRef.current?.getBoundingClientRect();\n const actualWidth = rect?.width || 400;\n const actualHeight = rect?.height || 200;\n const edgeSize = Math.min(actualWidth, actualHeight) * (borderWidth * 0.5);\n\n const svgContent = `\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n `;\n\n return `data:image/svg+xml,${encodeURIComponent(svgContent)}`;\n };\n\n const updateDisplacementMap = () => {\n feImageRef.current?.setAttribute('href', generateDisplacementMap());\n };\n\n useEffect(() => {\n updateDisplacementMap();\n [\n { ref: redChannelRef, offset: redOffset },\n { ref: greenChannelRef, offset: greenOffset },\n { ref: blueChannelRef, offset: blueOffset }\n ].forEach(({ ref, offset }) => {\n if (ref.current) {\n ref.current.setAttribute('scale', (distortionScale + offset).toString());\n ref.current.setAttribute('xChannelSelector', xChannel);\n ref.current.setAttribute('yChannelSelector', yChannel);\n }\n });\n\n gaussianBlurRef.current?.setAttribute('stdDeviation', displace.toString());\n }, [\n width,\n height,\n borderRadius,\n borderWidth,\n brightness,\n opacity,\n blur,\n displace,\n distortionScale,\n redOffset,\n greenOffset,\n blueOffset,\n xChannel,\n yChannel,\n mixBlendMode\n ]);\n\n useEffect(() => {\n setSvgSupported(supportsSVGFilters());\n }, []);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const resizeObserver = new ResizeObserver(() => {\n setTimeout(updateDisplacementMap, 0);\n });\n\n resizeObserver.observe(containerRef.current);\n\n return () => {\n resizeObserver.disconnect();\n };\n }, []);\n\n useEffect(() => {\n setTimeout(updateDisplacementMap, 0);\n }, [width, height]);\n\n const supportsSVGFilters = () => {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return false;\n }\n\n const isWebkit = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);\n const isFirefox = /Firefox/.test(navigator.userAgent);\n\n if (isWebkit || isFirefox) {\n return false;\n }\n\n const div = document.createElement('div');\n div.style.backdropFilter = `url(#${filterId})`;\n\n return div.style.backdropFilter !== '';\n };\n\n const supportsBackdropFilter = () => {\n if (typeof window === 'undefined') return false;\n return CSS.supports('backdrop-filter', 'blur(10px)');\n };\n\n const getContainerStyles = (): React.CSSProperties => {\n const baseStyles: React.CSSProperties = {\n ...style,\n width: typeof width === 'number' ? `${width}px` : width,\n height: typeof height === 'number' ? `${height}px` : height,\n borderRadius: `${borderRadius}px`,\n '--glass-frost': backgroundOpacity,\n '--glass-saturation': saturation\n } as React.CSSProperties;\n\n const backdropFilterSupported = supportsBackdropFilter();\n\n if (svgSupported) {\n return {\n ...baseStyles,\n background: isDarkMode ? `hsl(0 0% 0% / ${backgroundOpacity})` : `hsl(0 0% 100% / ${backgroundOpacity})`,\n backdropFilter: `url(#${filterId}) saturate(${saturation})`,\n boxShadow: isDarkMode\n ? `0 0 2px 1px color-mix(in oklch, white, transparent 65%) inset,\n 0 0 10px 4px color-mix(in oklch, white, transparent 85%) inset,\n 0px 4px 16px rgba(17, 17, 26, 0.05),\n 0px 8px 24px rgba(17, 17, 26, 0.05),\n 0px 16px 56px rgba(17, 17, 26, 0.05),\n 0px 4px 16px rgba(17, 17, 26, 0.05) inset,\n 0px 8px 24px rgba(17, 17, 26, 0.05) inset,\n 0px 16px 56px rgba(17, 17, 26, 0.05) inset`\n : `0 0 2px 1px color-mix(in oklch, black, transparent 85%) inset,\n 0 0 10px 4px color-mix(in oklch, black, transparent 90%) inset,\n 0px 4px 16px rgba(17, 17, 26, 0.05),\n 0px 8px 24px rgba(17, 17, 26, 0.05),\n 0px 16px 56px rgba(17, 17, 26, 0.05),\n 0px 4px 16px rgba(17, 17, 26, 0.05) inset,\n 0px 8px 24px rgba(17, 17, 26, 0.05) inset,\n 0px 16px 56px rgba(17, 17, 26, 0.05) inset`\n };\n } else {\n if (isDarkMode) {\n if (!backdropFilterSupported) {\n return {\n ...baseStyles,\n background: 'rgba(0, 0, 0, 0.4)',\n border: '1px solid rgba(255, 255, 255, 0.2)',\n boxShadow: `inset 0 1px 0 0 rgba(255, 255, 255, 0.2),\n inset 0 -1px 0 0 rgba(255, 255, 255, 0.1)`\n };\n } else {\n return {\n ...baseStyles,\n background: 'rgba(255, 255, 255, 0.1)',\n backdropFilter: 'blur(12px) saturate(1.8) brightness(1.2)',\n WebkitBackdropFilter: 'blur(12px) saturate(1.8) brightness(1.2)',\n border: '1px solid rgba(255, 255, 255, 0.2)',\n boxShadow: `inset 0 1px 0 0 rgba(255, 255, 255, 0.2),\n inset 0 -1px 0 0 rgba(255, 255, 255, 0.1)`\n };\n }\n } else {\n if (!backdropFilterSupported) {\n return {\n ...baseStyles,\n background: 'rgba(255, 255, 255, 0.4)',\n border: '1px solid rgba(255, 255, 255, 0.3)',\n boxShadow: `inset 0 1px 0 0 rgba(255, 255, 255, 0.5),\n inset 0 -1px 0 0 rgba(255, 255, 255, 0.3)`\n };\n } else {\n return {\n ...baseStyles,\n background: 'rgba(255, 255, 255, 0.25)',\n backdropFilter: 'blur(12px) saturate(1.8) brightness(1.1)',\n WebkitBackdropFilter: 'blur(12px) saturate(1.8) brightness(1.1)',\n border: '1px solid rgba(255, 255, 255, 0.3)',\n boxShadow: `0 8px 32px 0 rgba(31, 38, 135, 0.2),\n 0 2px 16px 0 rgba(31, 38, 135, 0.1),\n inset 0 1px 0 0 rgba(255, 255, 255, 0.4),\n inset 0 -1px 0 0 rgba(255, 255, 255, 0.2)`\n };\n }\n }\n }\n };\n\n const glassSurfaceClasses =\n 'relative flex items-center justify-center overflow-hidden transition-opacity duration-[260ms] ease-out';\n\n const focusVisibleClasses = isDarkMode\n ? 'focus-visible:outline-2 focus-visible:outline-[#0A84FF] focus-visible:outline-offset-2'\n : 'focus-visible:outline-2 focus-visible:outline-[#007AFF] focus-visible:outline-offset-2';\n\n return (\n \n \n \n \n \n\n \n \n\n \n \n\n \n \n\n \n \n \n \n \n \n\n
\n {children}\n
\n \n );\n};\n\nexport default GlassSurface;\n" } ], "registryDependencies": [],