From c3e735e82d66cb5dbe63e83373b8079b3ac895e3 Mon Sep 17 00:00:00 2001 From: Abdimajid Shire Date: Tue, 31 Mar 2026 09:16:45 +0300 Subject: [PATCH 01/15] refactor(pulsating-button): remove unnecessary markup --- apps/www/public/llms-full.txt | 5 ++--- apps/www/public/r/pulsating-button.json | 2 +- apps/www/registry/magicui/pulsating-button.tsx | 5 ++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/www/public/llms-full.txt b/apps/www/public/llms-full.txt index 2a89c2f05..69eaea74b 100644 --- a/apps/www/public/llms-full.txt +++ b/apps/www/public/llms-full.txt @@ -12910,7 +12910,7 @@ export const PulsatingButton = React.forwardRef< ) } diff --git a/apps/www/public/r/pulsating-button.json b/apps/www/public/r/pulsating-button.json index 853f24b01..a6304c373 100644 --- a/apps/www/public/r/pulsating-button.json +++ b/apps/www/public/r/pulsating-button.json @@ -7,7 +7,7 @@ "files": [ { "path": "registry/magicui/pulsating-button.tsx", - "content": "import React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface PulsatingButtonProps extends React.ButtonHTMLAttributes {\n pulseColor?: string\n duration?: string\n}\n\nexport const PulsatingButton = React.forwardRef<\n HTMLButtonElement,\n PulsatingButtonProps\n>(\n (\n {\n className,\n children,\n pulseColor = \"#808080\",\n duration = \"1.5s\",\n ...props\n },\n ref\n ) => {\n return (\n \n
{children}
\n
\n \n )\n }\n)\n\nPulsatingButton.displayName = \"PulsatingButton\"\n", + "content": "import React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface PulsatingButtonProps extends React.ButtonHTMLAttributes {\n pulseColor?: string\n duration?: string\n}\n\nexport const PulsatingButton = React.forwardRef<\n HTMLButtonElement,\n PulsatingButtonProps\n>(\n (\n {\n className,\n children,\n pulseColor = \"#808080\",\n duration = \"1.5s\",\n ...props\n },\n ref\n ) => {\n return (\n \n {children}\n \n )\n }\n)\n\nPulsatingButton.displayName = \"PulsatingButton\"\n", "type": "registry:ui" } ], diff --git a/apps/www/registry/magicui/pulsating-button.tsx b/apps/www/registry/magicui/pulsating-button.tsx index 7e1254883..02d06c806 100644 --- a/apps/www/registry/magicui/pulsating-button.tsx +++ b/apps/www/registry/magicui/pulsating-button.tsx @@ -25,7 +25,7 @@ export const PulsatingButton = React.forwardRef< ) } From 4999253ad3acff6a00b3f21d9e9d6a44f63ef148 Mon Sep 17 00:00:00 2001 From: Abdimajid Shire Date: Sat, 4 Apr 2026 10:03:51 +0300 Subject: [PATCH 02/15] feat(pulsating-button): auto-derive pulse color from background & support all CSS color formats --- apps/www/public/llms-full.txt | 24 +++++-- apps/www/public/r/pulsating-button.json | 2 +- .../www/registry/magicui/pulsating-button.tsx | 65 ++++++++++--------- apps/www/styles/globals.css | 13 ++-- 4 files changed, 57 insertions(+), 47 deletions(-) diff --git a/apps/www/public/llms-full.txt b/apps/www/public/llms-full.txt index 69eaea74b..4eb556846 100644 --- a/apps/www/public/llms-full.txt +++ b/apps/www/public/llms-full.txt @@ -12883,7 +12883,9 @@ Title: Pulsating Button Description: An animated pulsating button useful for capturing attention of users. --- file: magicui/pulsating-button.tsx --- -import React from "react" +"use client" + +import React, { useEffect, useImperativeHandle, useRef } from "react" import { cn } from "@/lib/utils" @@ -12900,22 +12902,34 @@ export const PulsatingButton = React.forwardRef< { className, children, - pulseColor = "#808080", + pulseColor, duration = "1.5s", ...props }, ref ) => { + + const innerRef = useRef(null) + useImperativeHandle(ref, () => innerRef.current!); + + + useEffect(() => { + if (!innerRef.current) return + + const bg = getComputedStyle(innerRef.current).backgroundColor; + innerRef.current.style.setProperty("--bg", bg); + }, []) + return ( \n )\n }\n)\n\nPulsatingButton.displayName = \"PulsatingButton\"\n", + "content": "\"use client\"\n\nimport React, { useEffect, useImperativeHandle, useRef } from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface PulsatingButtonProps extends React.ButtonHTMLAttributes {\n pulseColor?: string\n duration?: string\n}\n\nexport const PulsatingButton = React.forwardRef<\n HTMLButtonElement,\n PulsatingButtonProps\n>(({ className, children, pulseColor, duration = \"1.5s\", ...props }, ref) => {\n const innerRef = useRef(null)\n useImperativeHandle(ref, () => innerRef.current!)\n\n useEffect(() => {\n if (!innerRef.current) return\n\n const bg = getComputedStyle(innerRef.current).backgroundColor\n innerRef.current.style.setProperty(\"--bg\", bg)\n }, [])\n\n return (\n \n {children}\n \n )\n})\n\nPulsatingButton.displayName = \"PulsatingButton\"\n", "type": "registry:ui" } ], diff --git a/apps/www/registry/magicui/pulsating-button.tsx b/apps/www/registry/magicui/pulsating-button.tsx index 02d06c806..02c001555 100644 --- a/apps/www/registry/magicui/pulsating-button.tsx +++ b/apps/www/registry/magicui/pulsating-button.tsx @@ -1,4 +1,6 @@ -import React from "react" +"use client" + +import React, { useEffect, useImperativeHandle, useRef } from "react" import { cn } from "@/lib/utils" @@ -10,36 +12,35 @@ interface PulsatingButtonProps extends React.ButtonHTMLAttributes( - ( - { - className, - children, - pulseColor = "#808080", - duration = "1.5s", - ...props - }, - ref - ) => { - return ( - - ) - } -) +>(({ className, children, pulseColor, duration = "1.5s", ...props }, ref) => { + const innerRef = useRef(null) + useImperativeHandle(ref, () => innerRef.current!) + + useEffect(() => { + if (!innerRef.current) return + + const bg = getComputedStyle(innerRef.current).backgroundColor + innerRef.current.style.setProperty("--bg", bg) + }, []) + + return ( + + ) +}) PulsatingButton.displayName = "PulsatingButton" diff --git a/apps/www/styles/globals.css b/apps/www/styles/globals.css index e4d249b9f..f3760c901 100644 --- a/apps/www/styles/globals.css +++ b/apps/www/styles/globals.css @@ -363,15 +363,10 @@ } } - @keyframes pulse { - 0%, - 100% { - box-shadow: 0 0 0 0 var(--pulse-color); - } - 50% { - box-shadow: 0 0 0 8px var(--pulse-color); - } - } +@keyframes pulse { + 0%,100% {box-shadow: 0 0 0 0 var(--pulse-color, oklch(from var(--bg) l c h / 0.5));} + 50% {box-shadow: 0 0 0 8px var(--pulse-color, oklch(from var(--bg) l c h / 0.5));} +} @keyframes rainbow { 0% { From cedf448a15b7e7e881e6e09f14138f37f19f0470 Mon Sep 17 00:00:00 2001 From: Abdimajid Shire Date: Sat, 4 Apr 2026 10:13:18 +0300 Subject: [PATCH 03/15] feat(pulsating-button): add distance prop --- apps/www/public/llms-full.txt | 69 ++++++++--------- apps/www/public/r/pulsating-button.json | 2 +- .../www/registry/magicui/pulsating-button.tsx | 74 +++++++++++-------- apps/www/styles/globals.css | 2 +- 4 files changed, 75 insertions(+), 72 deletions(-) diff --git a/apps/www/public/llms-full.txt b/apps/www/public/llms-full.txt index 4eb556846..77feb306e 100644 --- a/apps/www/public/llms-full.txt +++ b/apps/www/public/llms-full.txt @@ -12892,54 +12892,43 @@ import { cn } from "@/lib/utils" interface PulsatingButtonProps extends React.ButtonHTMLAttributes { pulseColor?: string duration?: string + distance?: string } export const PulsatingButton = React.forwardRef< HTMLButtonElement, PulsatingButtonProps ->( - ( - { - className, - children, - pulseColor, - duration = "1.5s", - ...props - }, - ref - ) => { +>(({ className, children, pulseColor, duration = "1.5s", distance = "8px", ...props }, ref) => { + const innerRef = useRef(null) + useImperativeHandle(ref, () => innerRef.current!) - const innerRef = useRef(null) - useImperativeHandle(ref, () => innerRef.current!); - - - useEffect(() => { - if (!innerRef.current) return + useEffect(() => { + if (!innerRef.current) return - const bg = getComputedStyle(innerRef.current).backgroundColor; - innerRef.current.style.setProperty("--bg", bg); - }, []) + const bg = getComputedStyle(innerRef.current).backgroundColor + innerRef.current.style.setProperty("--bg", bg) + }, []) - return ( - - ) - } -) + return ( + + ) +}) PulsatingButton.displayName = "PulsatingButton" diff --git a/apps/www/public/r/pulsating-button.json b/apps/www/public/r/pulsating-button.json index 6a4dd6863..1ec04612e 100644 --- a/apps/www/public/r/pulsating-button.json +++ b/apps/www/public/r/pulsating-button.json @@ -7,7 +7,7 @@ "files": [ { "path": "registry/magicui/pulsating-button.tsx", - "content": "\"use client\"\n\nimport React, { useEffect, useImperativeHandle, useRef } from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface PulsatingButtonProps extends React.ButtonHTMLAttributes {\n pulseColor?: string\n duration?: string\n}\n\nexport const PulsatingButton = React.forwardRef<\n HTMLButtonElement,\n PulsatingButtonProps\n>(({ className, children, pulseColor, duration = \"1.5s\", ...props }, ref) => {\n const innerRef = useRef(null)\n useImperativeHandle(ref, () => innerRef.current!)\n\n useEffect(() => {\n if (!innerRef.current) return\n\n const bg = getComputedStyle(innerRef.current).backgroundColor\n innerRef.current.style.setProperty(\"--bg\", bg)\n }, [])\n\n return (\n \n {children}\n \n )\n})\n\nPulsatingButton.displayName = \"PulsatingButton\"\n", + "content": "\"use client\"\n\nimport React, { useEffect, useImperativeHandle, useRef } from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface PulsatingButtonProps extends React.ButtonHTMLAttributes {\n pulseColor?: string\n duration?: string\n distance?: string\n}\n\nexport const PulsatingButton = React.forwardRef<\n HTMLButtonElement,\n PulsatingButtonProps\n>(\n (\n {\n className,\n children,\n pulseColor,\n duration = \"1.5s\",\n distance = \"8px\",\n ...props\n },\n ref\n ) => {\n const innerRef = useRef(null)\n useImperativeHandle(ref, () => innerRef.current!)\n\n useEffect(() => {\n if (!innerRef.current) return\n\n const bg = getComputedStyle(innerRef.current).backgroundColor\n innerRef.current.style.setProperty(\"--bg\", bg)\n }, [])\n\n return (\n \n {children}\n \n )\n }\n)\n\nPulsatingButton.displayName = \"PulsatingButton\"\n", "type": "registry:ui" } ], diff --git a/apps/www/registry/magicui/pulsating-button.tsx b/apps/www/registry/magicui/pulsating-button.tsx index 02c001555..753a3c50b 100644 --- a/apps/www/registry/magicui/pulsating-button.tsx +++ b/apps/www/registry/magicui/pulsating-button.tsx @@ -7,40 +7,54 @@ import { cn } from "@/lib/utils" interface PulsatingButtonProps extends React.ButtonHTMLAttributes { pulseColor?: string duration?: string + distance?: string } export const PulsatingButton = React.forwardRef< HTMLButtonElement, PulsatingButtonProps ->(({ className, children, pulseColor, duration = "1.5s", ...props }, ref) => { - const innerRef = useRef(null) - useImperativeHandle(ref, () => innerRef.current!) - - useEffect(() => { - if (!innerRef.current) return - - const bg = getComputedStyle(innerRef.current).backgroundColor - innerRef.current.style.setProperty("--bg", bg) - }, []) - - return ( - - ) -}) +>( + ( + { + className, + children, + pulseColor, + duration = "1.5s", + distance = "8px", + ...props + }, + ref + ) => { + const innerRef = useRef(null) + useImperativeHandle(ref, () => innerRef.current!) + + useEffect(() => { + if (!innerRef.current) return + + const bg = getComputedStyle(innerRef.current).backgroundColor + innerRef.current.style.setProperty("--bg", bg) + }, []) + + return ( + + ) + } +) PulsatingButton.displayName = "PulsatingButton" diff --git a/apps/www/styles/globals.css b/apps/www/styles/globals.css index f3760c901..7432187ff 100644 --- a/apps/www/styles/globals.css +++ b/apps/www/styles/globals.css @@ -365,7 +365,7 @@ @keyframes pulse { 0%,100% {box-shadow: 0 0 0 0 var(--pulse-color, oklch(from var(--bg) l c h / 0.5));} - 50% {box-shadow: 0 0 0 8px var(--pulse-color, oklch(from var(--bg) l c h / 0.5));} + 50% {box-shadow: 0 0 0 var(--distance) var(--pulse-color, oklch(from var(--bg) l c h / 0.5));} } @keyframes rainbow { From 3047f03efd568c3076b6b30212322b5cf0f680dc Mon Sep 17 00:00:00 2001 From: Abdimajid Shire Date: Sat, 4 Apr 2026 11:19:41 +0300 Subject: [PATCH 04/15] feat(pulsating-button): add pulse-ripple animation variant --- apps/www/public/llms-full.txt | 71 +++++++++++-------- apps/www/public/r/pulsating-button.json | 2 +- .../www/registry/magicui/pulsating-button.tsx | 5 +- apps/www/styles/globals.css | 6 ++ 4 files changed, 54 insertions(+), 30 deletions(-) diff --git a/apps/www/public/llms-full.txt b/apps/www/public/llms-full.txt index 77feb306e..f7994f137 100644 --- a/apps/www/public/llms-full.txt +++ b/apps/www/public/llms-full.txt @@ -12893,42 +12893,57 @@ interface PulsatingButtonProps extends React.ButtonHTMLAttributes(({ className, children, pulseColor, duration = "1.5s", distance = "8px", ...props }, ref) => { - const innerRef = useRef(null) - useImperativeHandle(ref, () => innerRef.current!) +>( + ( + { + className, + children, + pulseColor, + duration = "1.5s", + distance = "8px", + variant = "pulse", + ...props + }, + ref + ) => { + const innerRef = useRef(null) + useImperativeHandle(ref, () => innerRef.current!) - useEffect(() => { - if (!innerRef.current) return + useEffect(() => { + if (!innerRef.current) return - const bg = getComputedStyle(innerRef.current).backgroundColor - innerRef.current.style.setProperty("--bg", bg) - }, []) + const bg = getComputedStyle(innerRef.current).backgroundColor + innerRef.current.style.setProperty("--bg", bg) + }, []) - return ( - - ) -}) + return ( + + ) + } +) PulsatingButton.displayName = "PulsatingButton" diff --git a/apps/www/public/r/pulsating-button.json b/apps/www/public/r/pulsating-button.json index 1ec04612e..8b735d3cb 100644 --- a/apps/www/public/r/pulsating-button.json +++ b/apps/www/public/r/pulsating-button.json @@ -7,7 +7,7 @@ "files": [ { "path": "registry/magicui/pulsating-button.tsx", - "content": "\"use client\"\n\nimport React, { useEffect, useImperativeHandle, useRef } from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface PulsatingButtonProps extends React.ButtonHTMLAttributes {\n pulseColor?: string\n duration?: string\n distance?: string\n}\n\nexport const PulsatingButton = React.forwardRef<\n HTMLButtonElement,\n PulsatingButtonProps\n>(\n (\n {\n className,\n children,\n pulseColor,\n duration = \"1.5s\",\n distance = \"8px\",\n ...props\n },\n ref\n ) => {\n const innerRef = useRef(null)\n useImperativeHandle(ref, () => innerRef.current!)\n\n useEffect(() => {\n if (!innerRef.current) return\n\n const bg = getComputedStyle(innerRef.current).backgroundColor\n innerRef.current.style.setProperty(\"--bg\", bg)\n }, [])\n\n return (\n \n {children}\n \n )\n }\n)\n\nPulsatingButton.displayName = \"PulsatingButton\"\n", + "content": "\"use client\"\n\nimport React, { useEffect, useImperativeHandle, useRef } from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface PulsatingButtonProps extends React.ButtonHTMLAttributes {\n pulseColor?: string\n duration?: string\n distance?: string\n variant?: \"pulse\" | \"ripple\"\n}\n\nexport const PulsatingButton = React.forwardRef<\n HTMLButtonElement,\n PulsatingButtonProps\n>(\n (\n {\n className,\n children,\n pulseColor,\n duration = \"1.5s\",\n distance = \"8px\",\n variant = \"pulse\",\n ...props\n },\n ref\n ) => {\n const innerRef = useRef(null)\n useImperativeHandle(ref, () => innerRef.current!)\n\n useEffect(() => {\n if (!innerRef.current) return\n\n const bg = getComputedStyle(innerRef.current).backgroundColor\n innerRef.current.style.setProperty(\"--bg\", bg)\n }, [])\n\n return (\n \n {children}\n \n )\n }\n)\n\nPulsatingButton.displayName = \"PulsatingButton\"\n", "type": "registry:ui" } ], diff --git a/apps/www/registry/magicui/pulsating-button.tsx b/apps/www/registry/magicui/pulsating-button.tsx index 753a3c50b..5c02c3374 100644 --- a/apps/www/registry/magicui/pulsating-button.tsx +++ b/apps/www/registry/magicui/pulsating-button.tsx @@ -8,6 +8,7 @@ interface PulsatingButtonProps extends React.ButtonHTMLAttributes Date: Sat, 4 Apr 2026 11:30:11 +0300 Subject: [PATCH 05/15] fix(pulsating-button): update pulse color on theme change --- apps/www/public/llms-full.txt | 23 +++++++++++++++---- apps/www/public/r/pulsating-button.json | 2 +- .../www/registry/magicui/pulsating-button.tsx | 17 ++++++++++++-- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/apps/www/public/llms-full.txt b/apps/www/public/llms-full.txt index f7994f137..3ba4f8d6e 100644 --- a/apps/www/public/llms-full.txt +++ b/apps/www/public/llms-full.txt @@ -12915,12 +12915,25 @@ export const PulsatingButton = React.forwardRef< const innerRef = useRef(null) useImperativeHandle(ref, () => innerRef.current!) - useEffect(() => { - if (!innerRef.current) return +useEffect(() => { + if (!innerRef.current) return - const bg = getComputedStyle(innerRef.current).backgroundColor - innerRef.current.style.setProperty("--bg", bg) - }, []) + const updateBg = () => { + if (!innerRef.current) return + const bg = getComputedStyle(innerRef.current).backgroundColor + innerRef.current.style.setProperty("--bg", bg) + } + + updateBg() + +const observer = new MutationObserver(updateBg) +observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ["class"], +}) + + return () => observer.disconnect() +}, []) return ( \n )\n }\n)\n\nPulsatingButton.displayName = \"PulsatingButton\"\n", + "content": "\"use client\"\n\nimport React, { useEffect, useImperativeHandle, useRef } from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface PulsatingButtonProps extends React.ButtonHTMLAttributes {\n pulseColor?: string\n duration?: string\n distance?: string\n variant?: \"pulse\" | \"ripple\"\n}\n\nexport const PulsatingButton = React.forwardRef<\n HTMLButtonElement,\n PulsatingButtonProps\n>(\n (\n {\n className,\n children,\n pulseColor,\n duration = \"1.5s\",\n distance = \"8px\",\n variant = \"pulse\",\n ...props\n },\n ref\n ) => {\n const innerRef = useRef(null)\n useImperativeHandle(ref, () => innerRef.current!)\n\n useEffect(() => {\n if (!innerRef.current) return\n\n const updateBg = () => {\n if (!innerRef.current) return\n const bg = getComputedStyle(innerRef.current).backgroundColor\n innerRef.current.style.setProperty(\"--bg\", bg)\n }\n\n updateBg()\n\n const observer = new MutationObserver(updateBg)\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"class\"],\n })\n\n return () => observer.disconnect()\n }, [])\n\n return (\n \n {children}\n \n )\n }\n)\n\nPulsatingButton.displayName = \"PulsatingButton\"\n", "type": "registry:ui" } ], diff --git a/apps/www/registry/magicui/pulsating-button.tsx b/apps/www/registry/magicui/pulsating-button.tsx index 5c02c3374..15dc64a0b 100644 --- a/apps/www/registry/magicui/pulsating-button.tsx +++ b/apps/www/registry/magicui/pulsating-button.tsx @@ -33,8 +33,21 @@ export const PulsatingButton = React.forwardRef< useEffect(() => { if (!innerRef.current) return - const bg = getComputedStyle(innerRef.current).backgroundColor - innerRef.current.style.setProperty("--bg", bg) + const updateBg = () => { + if (!innerRef.current) return + const bg = getComputedStyle(innerRef.current).backgroundColor + innerRef.current.style.setProperty("--bg", bg) + } + + updateBg() + + const observer = new MutationObserver(updateBg) + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ["class"], + }) + + return () => observer.disconnect() }, []) return ( From 6e0f4466141a603f214d367f5f3176bd6ef93277 Mon Sep 17 00:00:00 2001 From: Abdimajid Shire Date: Sat, 4 Apr 2026 12:40:10 +0300 Subject: [PATCH 06/15] docs(pulsating-button): add demo component & update docs --- .../docs/components/pulsating-button.mdx | 42 ++++++++++------- apps/www/public/llms-full.txt | 45 ++++++++++++------- apps/www/public/llms.txt | 1 + .../www/public/r/pulsating-button-demo-2.json | 17 +++++++ apps/www/public/r/registry.json | 15 +++++++ apps/www/public/registry.json | 15 +++++++ apps/www/registry.json | 15 +++++++ apps/www/registry/__index__.tsx | 17 +++++++ .../example/pulsating-button-demo-2.tsx | 9 ++++ apps/www/registry/registry-examples.ts | 14 ++++++ 10 files changed, 159 insertions(+), 31 deletions(-) create mode 100644 apps/www/public/r/pulsating-button-demo-2.json create mode 100644 apps/www/registry/example/pulsating-button-demo-2.tsx diff --git a/apps/www/content/docs/components/pulsating-button.mdx b/apps/www/content/docs/components/pulsating-button.mdx index 6e216500f..ada5bf457 100644 --- a/apps/www/content/docs/components/pulsating-button.mdx +++ b/apps/www/content/docs/components/pulsating-button.mdx @@ -41,16 +41,17 @@ Add the following animations to your global CSS file. ```css title="app/globals.css" showLineNumbers {2, 4-12} @theme inline { --animate-pulse: pulse var(--duration) ease-out infinite; + --animate-pulse-ripple: pulse-ripple var(--duration) cubic-bezier(0.16, 1, 0.3, 1) infinite; - @keyframes pulse { - 0%, - 100% { - box-shadow: 0 0 0 0 var(--pulse-color); - } - 50% { - box-shadow: 0 0 0 8px var(--pulse-color); - } - } +@keyframes pulse { + 0%,100% {box-shadow: 0 0 0 0 var(--pulse-color, oklch(from var(--bg) l c h / 0.5));} + 50% {box-shadow: 0 0 0 var(--distance) var(--pulse-color, oklch(from var(--bg) l c h / 0.5));} +} + +@keyframes pulse-ripple { + 0% { box-shadow: 0px 0px 0px 0px oklch(from var(--pulse-color, var(--bg)) l c h / 1); } + 100% { box-shadow: 0px 0px 0px var(--distance) oklch(from var(--pulse-color, var(--bg)) l c h / 0); } +} } ``` @@ -70,15 +71,24 @@ import { PulsatingButton } from "@/components/ui/pulsating-button" Pulsating Button ``` +## Examples + +### Ripple Variant + + + ## Props -| Prop | Type | Default | Description | -| ------------ | ----------------- | ------- | -------------------------------------------------------- | -| `children` | `React.ReactNode` | `-` | The content of the button. | -| `className` | `string` | `-` | Additional class names for the button. | -| `pulseColor` | `string` | `-` | The rbg numbers only for the color of the pulsing waves. | -| `duration` | `string` | `-` | The time span of one pulse. | +| Prop | Type | Default | Description | +| ------------ | ---------------------- | -------------------------------- | -------------------------------------------------------- | +| `children` | `React.ReactNode` | `-` | The content of the button. | +| `className` | `string` | `-` | Additional class names for the button. | +| `pulseColor` | `string` | `Derived from button background` | Any valid CSS color for the pulsing waves. | +| `duration` | `string` | `1.5s` | The duration of one pulse. | +| `distance` | `string` | `8px` | How far the pulse expands from the button. | +| `variant` | `"pulse" \| "ripple"` | `pulse` | The pulse animation style. | + ## Credits -- Credit to [@shikhap04](https://github.com/shikhap04) +- Credit to [@shikhap04](https://github.com/shikhap04) & [@abdmjd1](https://github.com/abdmjd1) diff --git a/apps/www/public/llms-full.txt b/apps/www/public/llms-full.txt index 3ba4f8d6e..307a3ec85 100644 --- a/apps/www/public/llms-full.txt +++ b/apps/www/public/llms-full.txt @@ -12915,25 +12915,25 @@ export const PulsatingButton = React.forwardRef< const innerRef = useRef(null) useImperativeHandle(ref, () => innerRef.current!) -useEffect(() => { - if (!innerRef.current) return + useEffect(() => { + if (!innerRef.current) return - const updateBg = () => { - if (!innerRef.current) return - const bg = getComputedStyle(innerRef.current).backgroundColor - innerRef.current.style.setProperty("--bg", bg) - } + const updateBg = () => { + if (!innerRef.current) return + const bg = getComputedStyle(innerRef.current).backgroundColor + innerRef.current.style.setProperty("--bg", bg) + } - updateBg() + updateBg() -const observer = new MutationObserver(updateBg) -observer.observe(document.documentElement, { - attributes: true, - attributeFilter: ["class"], -}) + const observer = new MutationObserver(updateBg) + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ["class"], + }) - return () => observer.disconnect() -}, []) + return () => observer.disconnect() + }, []) return ( \n )\n }\n)\n\nPulsatingButton.displayName = \"PulsatingButton\"\n", + "content": "\"use client\"\n\nimport React, { useEffect, useImperativeHandle, useRef } from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface PulsatingButtonProps extends React.ButtonHTMLAttributes {\n pulseColor?: string\n duration?: string\n distance?: string\n variant?: \"pulse\" | \"ripple\"\n}\n\nexport const PulsatingButton = React.forwardRef<\n HTMLButtonElement,\n PulsatingButtonProps\n>(\n (\n {\n className,\n children,\n pulseColor,\n duration = \"1.5s\",\n distance = \"8px\",\n variant = \"pulse\",\n ...props\n },\n ref\n ) => {\n const innerRef = useRef(null)\n useImperativeHandle(ref, () => innerRef.current!)\n\n useEffect(() => {\n const button = innerRef.current\n if (!button) return\n\n if (pulseColor) {\n button.style.removeProperty(\"--bg\")\n return\n }\n\n let animationFrameId = 0\n let currentBg = \"\"\n\n const updateBg = () => {\n animationFrameId = 0\n\n const nextBg = getComputedStyle(button).backgroundColor\n if (nextBg === currentBg) return\n\n currentBg = nextBg\n button.style.setProperty(\"--bg\", nextBg)\n }\n\n const scheduleBgUpdate = () => {\n if (animationFrameId) return\n animationFrameId = window.requestAnimationFrame(updateBg)\n }\n\n updateBg()\n\n const themeObserver = new MutationObserver(scheduleBgUpdate)\n themeObserver.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"class\"],\n })\n\n const buttonObserver = new MutationObserver(scheduleBgUpdate)\n buttonObserver.observe(button, {\n attributes: true,\n })\n\n const syncEvents = [\n \"blur\",\n \"focus\",\n \"pointerenter\",\n \"pointerleave\",\n ] as const\n\n for (const eventName of syncEvents) {\n button.addEventListener(eventName, scheduleBgUpdate)\n }\n\n return () => {\n if (animationFrameId) {\n window.cancelAnimationFrame(animationFrameId)\n }\n\n themeObserver.disconnect()\n buttonObserver.disconnect()\n\n for (const eventName of syncEvents) {\n button.removeEventListener(eventName, scheduleBgUpdate)\n }\n }\n }, [pulseColor])\n\n return (\n \n {children}\n \n )\n }\n)\n\nPulsatingButton.displayName = \"PulsatingButton\"\n", "type": "registry:ui" } ], diff --git a/apps/www/registry/magicui/pulsating-button.tsx b/apps/www/registry/magicui/pulsating-button.tsx index 15dc64a0b..dc182d690 100644 --- a/apps/www/registry/magicui/pulsating-button.tsx +++ b/apps/www/registry/magicui/pulsating-button.tsx @@ -31,24 +31,69 @@ export const PulsatingButton = React.forwardRef< useImperativeHandle(ref, () => innerRef.current!) useEffect(() => { - if (!innerRef.current) return + const button = innerRef.current + if (!button) return + + if (pulseColor) { + button.style.removeProperty("--bg") + return + } + + let animationFrameId = 0 + let currentBg = "" const updateBg = () => { - if (!innerRef.current) return - const bg = getComputedStyle(innerRef.current).backgroundColor - innerRef.current.style.setProperty("--bg", bg) + animationFrameId = 0 + + const nextBg = getComputedStyle(button).backgroundColor + if (nextBg === currentBg) return + + currentBg = nextBg + button.style.setProperty("--bg", nextBg) + } + + const scheduleBgUpdate = () => { + if (animationFrameId) return + animationFrameId = window.requestAnimationFrame(updateBg) } updateBg() - const observer = new MutationObserver(updateBg) - observer.observe(document.documentElement, { + const themeObserver = new MutationObserver(scheduleBgUpdate) + themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ["class"], }) - return () => observer.disconnect() - }, []) + const buttonObserver = new MutationObserver(scheduleBgUpdate) + buttonObserver.observe(button, { + attributes: true, + }) + + const syncEvents = [ + "blur", + "focus", + "pointerenter", + "pointerleave", + ] as const + + for (const eventName of syncEvents) { + button.addEventListener(eventName, scheduleBgUpdate) + } + + return () => { + if (animationFrameId) { + window.cancelAnimationFrame(animationFrameId) + } + + themeObserver.disconnect() + buttonObserver.disconnect() + + for (const eventName of syncEvents) { + button.removeEventListener(eventName, scheduleBgUpdate) + } + } + }, [pulseColor]) return ( ) } diff --git a/apps/www/public/r/pulsating-button.json b/apps/www/public/r/pulsating-button.json index 031d33815..f091f31b5 100644 --- a/apps/www/public/r/pulsating-button.json +++ b/apps/www/public/r/pulsating-button.json @@ -7,7 +7,7 @@ "files": [ { "path": "registry/magicui/pulsating-button.tsx", - "content": "\"use client\"\n\nimport React, { useEffect, useImperativeHandle, useRef } from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface PulsatingButtonProps extends React.ButtonHTMLAttributes {\n pulseColor?: string\n duration?: string\n distance?: string\n variant?: \"pulse\" | \"ripple\"\n}\n\nexport const PulsatingButton = React.forwardRef<\n HTMLButtonElement,\n PulsatingButtonProps\n>(\n (\n {\n className,\n children,\n pulseColor,\n duration = \"1.5s\",\n distance = \"8px\",\n variant = \"pulse\",\n ...props\n },\n ref\n ) => {\n const innerRef = useRef(null)\n useImperativeHandle(ref, () => innerRef.current!)\n\n useEffect(() => {\n const button = innerRef.current\n if (!button) return\n\n if (pulseColor) {\n button.style.removeProperty(\"--bg\")\n return\n }\n\n let animationFrameId = 0\n let currentBg = \"\"\n\n const updateBg = () => {\n animationFrameId = 0\n\n const nextBg = getComputedStyle(button).backgroundColor\n if (nextBg === currentBg) return\n\n currentBg = nextBg\n button.style.setProperty(\"--bg\", nextBg)\n }\n\n const scheduleBgUpdate = () => {\n if (animationFrameId) return\n animationFrameId = window.requestAnimationFrame(updateBg)\n }\n\n updateBg()\n\n const themeObserver = new MutationObserver(scheduleBgUpdate)\n themeObserver.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"class\"],\n })\n\n const buttonObserver = new MutationObserver(scheduleBgUpdate)\n buttonObserver.observe(button, {\n attributes: true,\n })\n\n const syncEvents = [\n \"blur\",\n \"focus\",\n \"pointerenter\",\n \"pointerleave\",\n ] as const\n\n for (const eventName of syncEvents) {\n button.addEventListener(eventName, scheduleBgUpdate)\n }\n\n return () => {\n if (animationFrameId) {\n window.cancelAnimationFrame(animationFrameId)\n }\n\n themeObserver.disconnect()\n buttonObserver.disconnect()\n\n for (const eventName of syncEvents) {\n button.removeEventListener(eventName, scheduleBgUpdate)\n }\n }\n }, [pulseColor])\n\n return (\n \n {children}\n \n )\n }\n)\n\nPulsatingButton.displayName = \"PulsatingButton\"\n", + "content": "\"use client\"\n\nimport React, { useEffect, useImperativeHandle, useRef } from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface PulsatingButtonProps extends React.ButtonHTMLAttributes {\n pulseColor?: string\n duration?: string\n distance?: string\n variant?: \"pulse\" | \"ripple\"\n}\n\nexport const PulsatingButton = React.forwardRef<\n HTMLButtonElement,\n PulsatingButtonProps\n>(\n (\n {\n className,\n children,\n pulseColor,\n duration = \"1.5s\",\n distance = \"8px\",\n variant = \"pulse\",\n ...props\n },\n ref\n ) => {\n const innerRef = useRef(null)\n useImperativeHandle(ref, () => innerRef.current!)\n\n useEffect(() => {\n const button = innerRef.current\n if (!button) return\n\n if (pulseColor) {\n button.style.removeProperty(\"--bg\")\n return\n }\n\n let animationFrameId = 0\n let currentBg = \"\"\n\n const updateBg = () => {\n animationFrameId = 0\n\n const nextBg = getComputedStyle(button).backgroundColor\n if (nextBg === currentBg) return\n\n currentBg = nextBg\n button.style.setProperty(\"--bg\", nextBg)\n }\n\n const scheduleBgUpdate = () => {\n if (animationFrameId) return\n animationFrameId = window.requestAnimationFrame(updateBg)\n }\n\n updateBg()\n\n const themeObserver = new MutationObserver(scheduleBgUpdate)\n themeObserver.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"class\"],\n })\n\n const buttonObserver = new MutationObserver(scheduleBgUpdate)\n buttonObserver.observe(button, {\n attributes: true,\n })\n\n const syncEvents = [\n \"blur\",\n \"focus\",\n \"pointerenter\",\n \"pointerleave\",\n ] as const\n\n for (const eventName of syncEvents) {\n button.addEventListener(eventName, scheduleBgUpdate)\n }\n\n return () => {\n if (animationFrameId) {\n window.cancelAnimationFrame(animationFrameId)\n }\n\n themeObserver.disconnect()\n buttonObserver.disconnect()\n\n for (const eventName of syncEvents) {\n button.removeEventListener(eventName, scheduleBgUpdate)\n }\n }\n }, [pulseColor])\n\n return (\n \n {children}\n \n \n )\n }\n)\n\nPulsatingButton.displayName = \"PulsatingButton\"\n", "type": "registry:ui" } ], diff --git a/apps/www/registry/magicui/pulsating-button.tsx b/apps/www/registry/magicui/pulsating-button.tsx index dc182d690..53f6d4879 100644 --- a/apps/www/registry/magicui/pulsating-button.tsx +++ b/apps/www/registry/magicui/pulsating-button.tsx @@ -99,8 +99,7 @@ export const PulsatingButton = React.forwardRef< ) } From 1c0d1a68484682311650455db7adc88154fcec9b Mon Sep 17 00:00:00 2001 From: Jinho Yeom Date: Sat, 4 Apr 2026 20:38:56 +0900 Subject: [PATCH 12/15] docs(pulsating-button): normalize css and props table formatting --- .omc/project-memory.json | 9 +++- .omc/state/hud-stdin-cache.json | 2 +- .../docs/components/pulsating-button.mdx | 43 +++++++++++-------- apps/www/styles/globals.css | 25 +++++++---- 4 files changed, 52 insertions(+), 27 deletions(-) diff --git a/.omc/project-memory.json b/.omc/project-memory.json index 4f9b50c83..bf63dc3cb 100644 --- a/.omc/project-memory.json +++ b/.omc/project-memory.json @@ -70,6 +70,13 @@ "keyFiles": [] } }, - "hotPaths": [], + "hotPaths": [ + { + "path": "apps/www/registry/magicui/pulsating-button.tsx", + "accessCount": 1, + "lastAccessed": 1775302716709, + "type": "file" + } + ], "userDirectives": [] } \ No newline at end of file diff --git a/.omc/state/hud-stdin-cache.json b/.omc/state/hud-stdin-cache.json index b248fd470..fac0a35f1 100644 --- a/.omc/state/hud-stdin-cache.json +++ b/.omc/state/hud-stdin-cache.json @@ -1 +1 @@ -{"session_id":"e66ad0ca-9c97-4031-857e-fefa0bce9e16","transcript_path":"/Users/yeomjinho/.claude/projects/-Users-yeomjinho-github-magicui/e66ad0ca-9c97-4031-857e-fefa0bce9e16.jsonl","cwd":"/Users/yeomjinho/github/magicui","model":{"id":"claude-sonnet-4-6","display_name":"Sonnet 4.6"},"workspace":{"current_dir":"/Users/yeomjinho/github/magicui","project_dir":"/Users/yeomjinho/github/magicui","added_dirs":[]},"version":"2.1.92","output_style":{"name":"default"},"cost":{"total_cost_usd":0.27504605000000004,"total_duration_ms":142595,"total_api_duration_ms":93788,"total_lines_added":0,"total_lines_removed":0},"context_window":{"total_input_tokens":370,"total_output_tokens":4556,"context_window_size":200000,"current_usage":{"input_tokens":3,"output_tokens":280,"cache_creation_input_tokens":15206,"cache_read_input_tokens":20786},"used_percentage":18,"remaining_percentage":82},"exceeds_200k_tokens":false,"rate_limits":{"five_hour":{"used_percentage":23,"resets_at":1775318400},"seven_day":{"used_percentage":7.000000000000001,"resets_at":1775811600}}} \ No newline at end of file +{"session_id":"e66ad0ca-9c97-4031-857e-fefa0bce9e16","transcript_path":"/Users/yeomjinho/.claude/projects/-Users-yeomjinho-github-magicui/e66ad0ca-9c97-4031-857e-fefa0bce9e16.jsonl","cwd":"/Users/yeomjinho/github/magicui","model":{"id":"claude-sonnet-4-6","display_name":"Sonnet 4.6"},"workspace":{"current_dir":"/Users/yeomjinho/github/magicui","project_dir":"/Users/yeomjinho/github/magicui","added_dirs":[]},"version":"2.1.92","output_style":{"name":"default"},"cost":{"total_cost_usd":0.3719894,"total_duration_ms":348219,"total_api_duration_ms":161386,"total_lines_added":0,"total_lines_removed":0},"context_window":{"total_input_tokens":376,"total_output_tokens":8983,"context_window_size":200000,"current_usage":{"input_tokens":3,"output_tokens":8,"cache_creation_input_tokens":5485,"cache_read_input_tokens":38347},"used_percentage":22,"remaining_percentage":78},"exceeds_200k_tokens":false,"rate_limits":{"five_hour":{"used_percentage":24,"resets_at":1775318400},"seven_day":{"used_percentage":7.000000000000001,"resets_at":1775811600}}} \ No newline at end of file diff --git a/apps/www/content/docs/components/pulsating-button.mdx b/apps/www/content/docs/components/pulsating-button.mdx index ada5bf457..1dbb3b385 100644 --- a/apps/www/content/docs/components/pulsating-button.mdx +++ b/apps/www/content/docs/components/pulsating-button.mdx @@ -43,15 +43,24 @@ Add the following animations to your global CSS file. --animate-pulse: pulse var(--duration) ease-out infinite; --animate-pulse-ripple: pulse-ripple var(--duration) cubic-bezier(0.16, 1, 0.3, 1) infinite; -@keyframes pulse { - 0%,100% {box-shadow: 0 0 0 0 var(--pulse-color, oklch(from var(--bg) l c h / 0.5));} - 50% {box-shadow: 0 0 0 var(--distance) var(--pulse-color, oklch(from var(--bg) l c h / 0.5));} -} - -@keyframes pulse-ripple { - 0% { box-shadow: 0px 0px 0px 0px oklch(from var(--pulse-color, var(--bg)) l c h / 1); } - 100% { box-shadow: 0px 0px 0px var(--distance) oklch(from var(--pulse-color, var(--bg)) l c h / 0); } -} + @keyframes pulse { + 0%, + 100% { + box-shadow: 0 0 0 0 var(--pulse-color, oklch(from var(--bg) l c h / 0.5)); + } + 50% { + box-shadow: 0 0 0 var(--distance) var(--pulse-color, oklch(from var(--bg) l c h / 0.5)); + } + } + + @keyframes pulse-ripple { + 0% { + box-shadow: 0 0 0 0 oklch(from var(--pulse-color, var(--bg)) l c h / 1); + } + 100% { + box-shadow: 0 0 0 var(--distance) oklch(from var(--pulse-color, var(--bg)) l c h / 0); + } + } } ``` @@ -79,14 +88,14 @@ import { PulsatingButton } from "@/components/ui/pulsating-button" ## Props -| Prop | Type | Default | Description | -| ------------ | ---------------------- | -------------------------------- | -------------------------------------------------------- | -| `children` | `React.ReactNode` | `-` | The content of the button. | -| `className` | `string` | `-` | Additional class names for the button. | -| `pulseColor` | `string` | `Derived from button background` | Any valid CSS color for the pulsing waves. | -| `duration` | `string` | `1.5s` | The duration of one pulse. | -| `distance` | `string` | `8px` | How far the pulse expands from the button. | -| `variant` | `"pulse" \| "ripple"` | `pulse` | The pulse animation style. | +| Prop | Type | Default | Description | +| ------------ | --------------------- | -------------------------------- | -------------------------------------------- | +| `children` | `React.ReactNode` | `-` | The content of the button. | +| `className` | `string` | `-` | Additional class names for the button. | +| `pulseColor` | `string` | `Derived from button background` | Any valid CSS color for the pulsing waves. | +| `duration` | `string` | `1.5s` | The duration of one pulse. | +| `distance` | `string` | `8px` | How far the pulse expands from the button. | +| `variant` | `"pulse" \| "ripple"` | `pulse` | The pulse animation style. | ## Credits diff --git a/apps/www/styles/globals.css b/apps/www/styles/globals.css index e99ab9bbb..cea7b8501 100644 --- a/apps/www/styles/globals.css +++ b/apps/www/styles/globals.css @@ -364,15 +364,24 @@ } } -@keyframes pulse { - 0%,100% {box-shadow: 0 0 0 0 var(--pulse-color, oklch(from var(--bg) l c h / 0.5));} - 50% {box-shadow: 0 0 0 var(--distance) var(--pulse-color, oklch(from var(--bg) l c h / 0.5));} -} + @keyframes pulse { + 0%, + 100% { + box-shadow: 0 0 0 0 var(--pulse-color, oklch(from var(--bg) l c h / 0.5)); + } + 50% { + box-shadow: 0 0 0 var(--distance) var(--pulse-color, oklch(from var(--bg) l c h / 0.5)); + } + } -@keyframes pulse-ripple { - 0% { box-shadow: 0px 0px 0px 0px oklch(from var(--pulse-color, var(--bg)) l c h / 1); } - 100% { box-shadow: 0px 0px 0px var(--distance) oklch(from var(--pulse-color, var(--bg)) l c h / 0); } -} + @keyframes pulse-ripple { + 0% { + box-shadow: 0 0 0 0 oklch(from var(--pulse-color, var(--bg)) l c h / 1); + } + 100% { + box-shadow: 0 0 0 var(--distance) oklch(from var(--pulse-color, var(--bg)) l c h / 0); + } + } @keyframes rainbow { 0% { From 15cc050d948b025e2312c4ad6f5c33eef2fd213f Mon Sep 17 00:00:00 2001 From: Jinho Yeom Date: Sat, 4 Apr 2026 20:42:09 +0900 Subject: [PATCH 13/15] chore: remove local claude/omc state files --- .claude/settings.local.json | 6 --- .omc/project-memory.json | 82 ----------------------------- .omc/state/hud-state.json | 6 --- .omc/state/hud-stdin-cache.json | 1 - .omc/state/idle-notif-cooldown.json | 3 -- 5 files changed, 98 deletions(-) delete mode 100644 .claude/settings.local.json delete mode 100644 .omc/project-memory.json delete mode 100644 .omc/state/hud-state.json delete mode 100644 .omc/state/hud-stdin-cache.json delete mode 100644 .omc/state/idle-notif-cooldown.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index f091b4618..000000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "enabledMcpjsonServers": [ - "next-devtools" - ], - "enableAllProjectMcpServers": true -} diff --git a/.omc/project-memory.json b/.omc/project-memory.json deleted file mode 100644 index bf63dc3cb..000000000 --- a/.omc/project-memory.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "version": "1.0.0", - "lastScanned": 1775302387887, - "projectRoot": "/Users/yeomjinho/github/magicui", - "techStack": { - "languages": [ - { - "name": "JavaScript/TypeScript", - "version": ">=22.14.0", - "confidence": "high", - "markers": [ - "package.json" - ] - } - ], - "frameworks": [], - "packageManager": "pnpm", - "runtime": "Node.js 22.14.0" - }, - "build": { - "buildCommand": "pnpm build", - "testCommand": null, - "lintCommand": "pnpm lint", - "devCommand": "pnpm dev", - "scripts": { - "preinstall": "npx only-allow pnpm", - "dev": "turbo run dev --parallel", - "build": "turbo run build", - "start": "turbo run start", - "lint": "turbo run lint", - "lint:fix": "turbo run lint:fix", - "typecheck": "turbo run typecheck", - "format:fix": "turbo run format:fix", - "format:fix:silent": "turbo run format:fix -- --log-level silent", - "format:check": "turbo run format:check", - "registry-deps:check": "pnpm --filter=www registry-deps:check", - "build:registry": "pnpm --filter=www build:registry && pnpm --filter=www lint:fix && pnpm format:fix:silent", - "check": "pnpm lint && pnpm typecheck && pnpm format:check && pnpm registry-deps:check" - } - }, - "conventions": { - "namingStyle": null, - "importStyle": null, - "testPattern": null, - "fileOrganization": null - }, - "structure": { - "isMonorepo": true, - "workspaces": [], - "mainDirectories": [], - "gitBranches": { - "defaultBranch": "main", - "branchingStrategy": null - } - }, - "customNotes": [], - "directoryMap": { - "apps": { - "path": "apps", - "purpose": null, - "fileCount": 2, - "lastAccessed": 1775302387834, - "keyFiles": [] - }, - "skills": { - "path": "skills", - "purpose": null, - "fileCount": 0, - "lastAccessed": 1775302387835, - "keyFiles": [] - } - }, - "hotPaths": [ - { - "path": "apps/www/registry/magicui/pulsating-button.tsx", - "accessCount": 1, - "lastAccessed": 1775302716709, - "type": "file" - } - ], - "userDirectives": [] -} \ No newline at end of file diff --git a/.omc/state/hud-state.json b/.omc/state/hud-state.json deleted file mode 100644 index 529a05b1e..000000000 --- a/.omc/state/hud-state.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "timestamp": "2026-04-04T11:33:24.096Z", - "backgroundTasks": [], - "sessionStartTimestamp": "2026-04-04T11:33:19.429Z", - "sessionId": "e66ad0ca-9c97-4031-857e-fefa0bce9e16" -} \ No newline at end of file diff --git a/.omc/state/hud-stdin-cache.json b/.omc/state/hud-stdin-cache.json deleted file mode 100644 index fac0a35f1..000000000 --- a/.omc/state/hud-stdin-cache.json +++ /dev/null @@ -1 +0,0 @@ -{"session_id":"e66ad0ca-9c97-4031-857e-fefa0bce9e16","transcript_path":"/Users/yeomjinho/.claude/projects/-Users-yeomjinho-github-magicui/e66ad0ca-9c97-4031-857e-fefa0bce9e16.jsonl","cwd":"/Users/yeomjinho/github/magicui","model":{"id":"claude-sonnet-4-6","display_name":"Sonnet 4.6"},"workspace":{"current_dir":"/Users/yeomjinho/github/magicui","project_dir":"/Users/yeomjinho/github/magicui","added_dirs":[]},"version":"2.1.92","output_style":{"name":"default"},"cost":{"total_cost_usd":0.3719894,"total_duration_ms":348219,"total_api_duration_ms":161386,"total_lines_added":0,"total_lines_removed":0},"context_window":{"total_input_tokens":376,"total_output_tokens":8983,"context_window_size":200000,"current_usage":{"input_tokens":3,"output_tokens":8,"cache_creation_input_tokens":5485,"cache_read_input_tokens":38347},"used_percentage":22,"remaining_percentage":78},"exceeds_200k_tokens":false,"rate_limits":{"five_hour":{"used_percentage":24,"resets_at":1775318400},"seven_day":{"used_percentage":7.000000000000001,"resets_at":1775811600}}} \ No newline at end of file diff --git a/.omc/state/idle-notif-cooldown.json b/.omc/state/idle-notif-cooldown.json deleted file mode 100644 index b71d7df29..000000000 --- a/.omc/state/idle-notif-cooldown.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "lastSentAt": "2026-04-04T11:34:45.365Z" -} \ No newline at end of file From 22562119d70b8c68db345ac62d26b9a4379e9aef Mon Sep 17 00:00:00 2001 From: Jinho Yeom Date: Sat, 4 Apr 2026 20:42:28 +0900 Subject: [PATCH 14/15] docs(pulsating-button): normalize css and props table formatting --- .../docs/components/pulsating-button.mdx | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/apps/www/content/docs/components/pulsating-button.mdx b/apps/www/content/docs/components/pulsating-button.mdx index 1dbb3b385..7814dd482 100644 --- a/apps/www/content/docs/components/pulsating-button.mdx +++ b/apps/www/content/docs/components/pulsating-button.mdx @@ -41,7 +41,8 @@ Add the following animations to your global CSS file. ```css title="app/globals.css" showLineNumbers {2, 4-12} @theme inline { --animate-pulse: pulse var(--duration) ease-out infinite; - --animate-pulse-ripple: pulse-ripple var(--duration) cubic-bezier(0.16, 1, 0.3, 1) infinite; + --animate-pulse-ripple: pulse-ripple var(--duration) + cubic-bezier(0.16, 1, 0.3, 1) infinite; @keyframes pulse { 0%, @@ -49,7 +50,8 @@ Add the following animations to your global CSS file. box-shadow: 0 0 0 0 var(--pulse-color, oklch(from var(--bg) l c h / 0.5)); } 50% { - box-shadow: 0 0 0 var(--distance) var(--pulse-color, oklch(from var(--bg) l c h / 0.5)); + box-shadow: 0 0 0 var(--distance) + var(--pulse-color, oklch(from var(--bg) l c h / 0.5)); } } @@ -58,7 +60,8 @@ Add the following animations to your global CSS file. box-shadow: 0 0 0 0 oklch(from var(--pulse-color, var(--bg)) l c h / 1); } 100% { - box-shadow: 0 0 0 var(--distance) oklch(from var(--pulse-color, var(--bg)) l c h / 0); + box-shadow: 0 0 0 var(--distance) + oklch(from var(--pulse-color, var(--bg)) l c h / 0); } } } @@ -88,15 +91,14 @@ import { PulsatingButton } from "@/components/ui/pulsating-button" ## Props -| Prop | Type | Default | Description | -| ------------ | --------------------- | -------------------------------- | -------------------------------------------- | -| `children` | `React.ReactNode` | `-` | The content of the button. | -| `className` | `string` | `-` | Additional class names for the button. | -| `pulseColor` | `string` | `Derived from button background` | Any valid CSS color for the pulsing waves. | -| `duration` | `string` | `1.5s` | The duration of one pulse. | -| `distance` | `string` | `8px` | How far the pulse expands from the button. | -| `variant` | `"pulse" \| "ripple"` | `pulse` | The pulse animation style. | - +| Prop | Type | Default | Description | +| ------------ | --------------------- | -------------------------------- | ------------------------------------------ | +| `children` | `React.ReactNode` | `-` | The content of the button. | +| `className` | `string` | `-` | Additional class names for the button. | +| `pulseColor` | `string` | `Derived from button background` | Any valid CSS color for the pulsing waves. | +| `duration` | `string` | `1.5s` | The duration of one pulse. | +| `distance` | `string` | `8px` | How far the pulse expands from the button. | +| `variant` | `"pulse" \| "ripple"` | `pulse` | The pulse animation style. | ## Credits From bacf52fff864a5add771bc0c4de4c57923c4759b Mon Sep 17 00:00:00 2001 From: Jinho Yeom Date: Sat, 4 Apr 2026 20:43:37 +0900 Subject: [PATCH 15/15] fix(pulsating-button): avoid initial derived color flash --- apps/www/public/llms-full.txt | 4 ++-- apps/www/public/r/pulsating-button.json | 2 +- apps/www/registry/magicui/pulsating-button.tsx | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/www/public/llms-full.txt b/apps/www/public/llms-full.txt index 57a2f5d0e..4c49bccda 100644 --- a/apps/www/public/llms-full.txt +++ b/apps/www/public/llms-full.txt @@ -12885,7 +12885,7 @@ Description: An animated pulsating button useful for capturing attention of user --- file: magicui/pulsating-button.tsx --- "use client" -import React, { useEffect, useImperativeHandle, useRef } from "react" +import React, { useImperativeHandle, useLayoutEffect, useRef } from "react" import { cn } from "@/lib/utils" @@ -12915,7 +12915,7 @@ export const PulsatingButton = React.forwardRef< const innerRef = useRef(null) useImperativeHandle(ref, () => innerRef.current!) - useEffect(() => { + useLayoutEffect(() => { const button = innerRef.current if (!button) return diff --git a/apps/www/public/r/pulsating-button.json b/apps/www/public/r/pulsating-button.json index f091f31b5..dff60452f 100644 --- a/apps/www/public/r/pulsating-button.json +++ b/apps/www/public/r/pulsating-button.json @@ -7,7 +7,7 @@ "files": [ { "path": "registry/magicui/pulsating-button.tsx", - "content": "\"use client\"\n\nimport React, { useEffect, useImperativeHandle, useRef } from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface PulsatingButtonProps extends React.ButtonHTMLAttributes {\n pulseColor?: string\n duration?: string\n distance?: string\n variant?: \"pulse\" | \"ripple\"\n}\n\nexport const PulsatingButton = React.forwardRef<\n HTMLButtonElement,\n PulsatingButtonProps\n>(\n (\n {\n className,\n children,\n pulseColor,\n duration = \"1.5s\",\n distance = \"8px\",\n variant = \"pulse\",\n ...props\n },\n ref\n ) => {\n const innerRef = useRef(null)\n useImperativeHandle(ref, () => innerRef.current!)\n\n useEffect(() => {\n const button = innerRef.current\n if (!button) return\n\n if (pulseColor) {\n button.style.removeProperty(\"--bg\")\n return\n }\n\n let animationFrameId = 0\n let currentBg = \"\"\n\n const updateBg = () => {\n animationFrameId = 0\n\n const nextBg = getComputedStyle(button).backgroundColor\n if (nextBg === currentBg) return\n\n currentBg = nextBg\n button.style.setProperty(\"--bg\", nextBg)\n }\n\n const scheduleBgUpdate = () => {\n if (animationFrameId) return\n animationFrameId = window.requestAnimationFrame(updateBg)\n }\n\n updateBg()\n\n const themeObserver = new MutationObserver(scheduleBgUpdate)\n themeObserver.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"class\"],\n })\n\n const buttonObserver = new MutationObserver(scheduleBgUpdate)\n buttonObserver.observe(button, {\n attributes: true,\n })\n\n const syncEvents = [\n \"blur\",\n \"focus\",\n \"pointerenter\",\n \"pointerleave\",\n ] as const\n\n for (const eventName of syncEvents) {\n button.addEventListener(eventName, scheduleBgUpdate)\n }\n\n return () => {\n if (animationFrameId) {\n window.cancelAnimationFrame(animationFrameId)\n }\n\n themeObserver.disconnect()\n buttonObserver.disconnect()\n\n for (const eventName of syncEvents) {\n button.removeEventListener(eventName, scheduleBgUpdate)\n }\n }\n }, [pulseColor])\n\n return (\n \n {children}\n \n \n )\n }\n)\n\nPulsatingButton.displayName = \"PulsatingButton\"\n", + "content": "\"use client\"\n\nimport React, { useImperativeHandle, useLayoutEffect, useRef } from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface PulsatingButtonProps extends React.ButtonHTMLAttributes {\n pulseColor?: string\n duration?: string\n distance?: string\n variant?: \"pulse\" | \"ripple\"\n}\n\nexport const PulsatingButton = React.forwardRef<\n HTMLButtonElement,\n PulsatingButtonProps\n>(\n (\n {\n className,\n children,\n pulseColor,\n duration = \"1.5s\",\n distance = \"8px\",\n variant = \"pulse\",\n ...props\n },\n ref\n ) => {\n const innerRef = useRef(null)\n useImperativeHandle(ref, () => innerRef.current!)\n\n useLayoutEffect(() => {\n const button = innerRef.current\n if (!button) return\n\n if (pulseColor) {\n button.style.removeProperty(\"--bg\")\n return\n }\n\n let animationFrameId = 0\n let currentBg = \"\"\n\n const updateBg = () => {\n animationFrameId = 0\n\n const nextBg = getComputedStyle(button).backgroundColor\n if (nextBg === currentBg) return\n\n currentBg = nextBg\n button.style.setProperty(\"--bg\", nextBg)\n }\n\n const scheduleBgUpdate = () => {\n if (animationFrameId) return\n animationFrameId = window.requestAnimationFrame(updateBg)\n }\n\n updateBg()\n\n const themeObserver = new MutationObserver(scheduleBgUpdate)\n themeObserver.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"class\"],\n })\n\n const buttonObserver = new MutationObserver(scheduleBgUpdate)\n buttonObserver.observe(button, {\n attributes: true,\n })\n\n const syncEvents = [\n \"blur\",\n \"focus\",\n \"pointerenter\",\n \"pointerleave\",\n ] as const\n\n for (const eventName of syncEvents) {\n button.addEventListener(eventName, scheduleBgUpdate)\n }\n\n return () => {\n if (animationFrameId) {\n window.cancelAnimationFrame(animationFrameId)\n }\n\n themeObserver.disconnect()\n buttonObserver.disconnect()\n\n for (const eventName of syncEvents) {\n button.removeEventListener(eventName, scheduleBgUpdate)\n }\n }\n }, [pulseColor])\n\n return (\n \n {children}\n \n \n )\n }\n)\n\nPulsatingButton.displayName = \"PulsatingButton\"\n", "type": "registry:ui" } ], diff --git a/apps/www/registry/magicui/pulsating-button.tsx b/apps/www/registry/magicui/pulsating-button.tsx index 53f6d4879..e2ea6142b 100644 --- a/apps/www/registry/magicui/pulsating-button.tsx +++ b/apps/www/registry/magicui/pulsating-button.tsx @@ -1,6 +1,6 @@ "use client" -import React, { useEffect, useImperativeHandle, useRef } from "react" +import React, { useImperativeHandle, useLayoutEffect, useRef } from "react" import { cn } from "@/lib/utils" @@ -30,7 +30,7 @@ export const PulsatingButton = React.forwardRef< const innerRef = useRef(null) useImperativeHandle(ref, () => innerRef.current!) - useEffect(() => { + useLayoutEffect(() => { const button = innerRef.current if (!button) return