Skip to content

Commit

Permalink
fix: button types and component export standard improvement (#492)
Browse files Browse the repository at this point in the history
* fix: button types and component export standard improvement

* fix: added correct ref in the buttons and made some improvement

* fix: remove unncessary code and making animated-subscribe-button accepts children

* feat: throw an error if animated-subscribe-button don't receive two <span> elements
  • Loading branch information
itsarghyadas authored Jan 16, 2025
1 parent abfe8e5 commit e0fbe78
Show file tree
Hide file tree
Showing 21 changed files with 220 additions and 202 deletions.
12 changes: 5 additions & 7 deletions content/docs/components/animated-subscribe-button.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,11 @@ npx shadcn@latest add "https://magicui.design/r/animated-subscribe-button"

## Props

| Prop | Type | Default | Description |
| ----------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `buttonColor` | `string` | `-` | The accent color for the button. This allows you to set a custom color that matches your brand's theme. |
| `buttonTextColor` | `string` | `-` | The color of the button text. This allows you to ensure the text is visible and matches your desired color scheme. |
| `subscribeStatus` | `boolean` | `-` | A boolean flag to check the condition for the button. This property can be used to toggle the button's state, such as subscribed or unsubscribed. |
| `initialText` | `string` | `-` | The initial text displayed on the button. This is useful for setting a default label when the button first appears. |
| `changeText` | `string` | `-` | The final text displayed on the button after an action has been taken. This can be used to indicate a state change, such as from "Subscribe" to "Subscribed". |
| Prop | Type | Default | Description |
| ----------------- | ----------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `subscribeStatus` | `boolean` | `false` | A boolean flag to check the condition for the button. This property can be used to toggle the button's state, such as subscribed or unsubscribed. |
| `children` | `React.ReactNode` | `-` | The content to be displayed inside the button. Should contain two children - first for unsubscribed state and second for subscribed state. |
| `className` | `string` | `-` | Optional class name to be applied to the button for custom styling. |

## Credits

Expand Down
2 changes: 1 addition & 1 deletion public/r/styles/default/animated-subscribe-button.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"files": [
{
"path": "magicui/animated-subscribe-button.tsx",
"content": "\"use client\";\n\nimport { AnimatePresence, motion } from \"motion/react\";\nimport React, { useState } from \"react\";\n\ninterface AnimatedSubscribeButtonProps {\n buttonColor: string;\n buttonTextColor?: string;\n subscribeStatus: boolean;\n initialText: React.ReactElement | string;\n changeText: React.ReactElement | string;\n}\n\nexport const AnimatedSubscribeButton: React.FC<\n AnimatedSubscribeButtonProps\n> = ({\n buttonColor,\n subscribeStatus,\n buttonTextColor,\n changeText,\n initialText,\n}) => {\n const [isSubscribed, setIsSubscribed] = useState<boolean>(subscribeStatus);\n\n return (\n <AnimatePresence mode=\"wait\">\n {isSubscribed ? (\n <motion.button\n className=\"relative flex h-10 w-[200px] items-center justify-center overflow-hidden rounded-md bg-white outline outline-1 outline-black\"\n onClick={() => setIsSubscribed(false)}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n >\n <motion.span\n key=\"action\"\n className=\"relative flex h-full w-full items-center justify-center font-semibold\"\n initial={{ y: -50 }}\n animate={{ y: 0 }}\n style={{ color: buttonColor }}\n >\n {changeText}\n </motion.span>\n </motion.button>\n ) : (\n <motion.button\n className=\"relative flex h-10 w-[200px] cursor-pointer items-center justify-center rounded-md border-none\"\n style={{ backgroundColor: buttonColor, color: buttonTextColor }}\n onClick={() => setIsSubscribed(true)}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n >\n <motion.span\n key=\"reaction\"\n className=\"relative flex items-center justify-center font-semibold\"\n initial={{ x: 0 }}\n exit={{ x: 50, transition: { duration: 0.1 } }}\n >\n {initialText}\n </motion.span>\n </motion.button>\n )}\n </AnimatePresence>\n );\n};\n",
"content": "\"use client\";\n\nimport { HTMLMotionProps } from \"motion/react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport React, { useState } from \"react\";\n\ninterface AnimatedSubscribeButtonProps extends HTMLMotionProps<\"button\"> {\n buttonColor: string;\n buttonTextColor?: string;\n subscribeStatus: boolean;\n initialText: React.ReactElement | string;\n changeText: React.ReactElement | string;\n ref?: React.Ref<HTMLButtonElement>;\n}\n\nexport const AnimatedSubscribeButton = React.forwardRef<\n HTMLButtonElement,\n AnimatedSubscribeButtonProps\n>(\n (\n {\n buttonColor,\n subscribeStatus,\n buttonTextColor,\n changeText,\n initialText,\n onClick,\n ...props\n },\n ref\n ) => {\n const [isSubscribed, setIsSubscribed] = useState<boolean>(subscribeStatus);\n\n return (\n <AnimatePresence mode=\"wait\">\n {isSubscribed ? (\n <motion.button\n ref={ref}\n className=\"relative flex h-10 w-[200px] items-center justify-center overflow-hidden rounded-md bg-white outline outline-1 outline-black\"\n onClick={(e: React.MouseEvent<HTMLButtonElement>) => {\n setIsSubscribed(false);\n onClick?.(e);\n }}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n {...props}\n >\n <motion.span\n ref={ref}\n key=\"action\"\n className=\"relative flex h-full w-full items-center justify-center font-semibold\"\n initial={{ y: -50 }}\n animate={{ y: 0 }}\n style={{ color: buttonColor }}\n >\n {changeText}\n </motion.span>\n </motion.button>\n ) : (\n <motion.button\n className=\"relative flex h-10 w-[200px] cursor-pointer items-center justify-center rounded-md border-none\"\n style={{ backgroundColor: buttonColor, color: buttonTextColor }}\n onClick={(e) => {\n setIsSubscribed(true);\n onClick?.(e);\n }}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n {...props}\n >\n <motion.span\n key=\"reaction\"\n className=\"relative flex items-center justify-center font-semibold\"\n initial={{ x: 0 }}\n exit={{ x: 50, transition: { duration: 0.1 } }}\n >\n {initialText}\n </motion.span>\n </motion.button>\n )}\n </AnimatePresence>\n );\n }\n);\n\nAnimatedSubscribeButton.displayName = \"AnimatedSubscribeButton\";\n",
"type": "registry:ui",
"target": ""
}
Expand Down
6 changes: 2 additions & 4 deletions public/r/styles/default/interactive-hover-button.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
{
"name": "interactive-hover-button",
"type": "registry:ui",
"dependencies": [
"lucide-react"
],
"dependencies": ["lucide-react"],
"files": [
{
"path": "magicui/interactive-hover-button.tsx",
Expand All @@ -12,4 +10,4 @@
"target": ""
}
]
}
}
2 changes: 1 addition & 1 deletion public/r/styles/default/pulsating-button.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"files": [
{
"path": "magicui/pulsating-button.tsx",
"content": "\"use client\";\n\nimport React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\ninterface PulsatingButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n pulseColor?: string;\n duration?: string;\n}\n\nexport default function PulsatingButton({\n className,\n children,\n pulseColor = \"#0096ff\",\n duration = \"1.5s\",\n ...props\n}: PulsatingButtonProps) {\n return (\n <button\n className={cn(\n \"relative flex cursor-pointer items-center justify-center rounded-lg bg-blue-500 px-4 py-2 text-center text-white dark:bg-blue-500 dark:text-black\",\n className,\n )}\n style={\n {\n \"--pulse-color\": pulseColor,\n \"--duration\": duration,\n } as React.CSSProperties\n }\n {...props}\n >\n <div className=\"relative z-10\">{children}</div>\n <div className=\"absolute left-1/2 top-1/2 size-full -translate-x-1/2 -translate-y-1/2 animate-pulse rounded-lg bg-inherit\" />\n </button>\n );\n}\n",
"content": "\"use client\";\n\nimport React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\ninterface PulsatingButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement> {\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 = \"#0096ff\",\n duration = \"1.5s\",\n ...props\n },\n ref,\n ) => {\n return (\n <button\n className={cn(\n \"relative flex cursor-pointer items-center justify-center rounded-lg bg-blue-500 px-4 py-2 text-center text-white dark:bg-blue-500 dark:text-black\",\n className,\n )}\n style={\n {\n \"--pulse-color\": pulseColor,\n \"--duration\": duration,\n } as React.CSSProperties\n }\n {...props}\n >\n <div className=\"relative z-10\">{children}</div>\n <div className=\"absolute left-1/2 top-1/2 size-full -translate-x-1/2 -translate-y-1/2 animate-pulse rounded-lg bg-inherit\" />\n </button>\n );\n },\n);\n\nPulsatingButton.displayName = \"PulsatingButton\";\n",
"type": "registry:ui",
"target": ""
}
Expand Down
2 changes: 1 addition & 1 deletion public/r/styles/default/rainbow-button.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"files": [
{
"path": "magicui/rainbow-button.tsx",
"content": "import React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\ninterface RainbowButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement> {}\n\nexport function RainbowButton({\n children,\n className,\n ...props\n}: RainbowButtonProps) {\n return (\n <button\n className={cn(\n \"group relative inline-flex h-11 animate-rainbow cursor-pointer items-center justify-center rounded-xl border-0 bg-[length:200%] px-8 py-2 font-medium text-primary-foreground transition-colors [background-clip:padding-box,border-box,border-box] [background-origin:border-box] [border:calc(0.08*1rem)_solid_transparent] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50\",\n\n // before styles\n \"before:absolute before:bottom-[-20%] before:left-1/2 before:z-0 before:h-1/5 before:w-3/5 before:-translate-x-1/2 before:animate-rainbow before:bg-[linear-gradient(90deg,hsl(var(--color-1)),hsl(var(--color-5)),hsl(var(--color-3)),hsl(var(--color-4)),hsl(var(--color-2)))] before:[filter:blur(calc(0.8*1rem))]\",\n\n // light mode colors\n \"bg-[linear-gradient(#121213,#121213),linear-gradient(#121213_50%,rgba(18,18,19,0.6)_80%,rgba(18,18,19,0)),linear-gradient(90deg,hsl(var(--color-1)),hsl(var(--color-5)),hsl(var(--color-3)),hsl(var(--color-4)),hsl(var(--color-2)))]\",\n\n // dark mode colors\n \"dark:bg-[linear-gradient(#fff,#fff),linear-gradient(#fff_50%,rgba(255,255,255,0.6)_80%,rgba(0,0,0,0)),linear-gradient(90deg,hsl(var(--color-1)),hsl(var(--color-5)),hsl(var(--color-3)),hsl(var(--color-4)),hsl(var(--color-2)))]\",\n\n className,\n )}\n {...props}\n >\n {children}\n </button>\n );\n}\n",
"content": "\"use client\";\n\nimport React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ninterface RainbowButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement> {}\n\nexport const RainbowButton = React.forwardRef<\n HTMLButtonElement,\n RainbowButtonProps\n>(({ children, className, ...props }, ref) => {\n return (\n <button\n ref={ref}\n className={cn(\n \"group relative inline-flex h-11 animate-rainbow cursor-pointer items-center justify-center rounded-xl border-0 bg-[length:200%] px-8 py-2 font-medium text-primary-foreground transition-colors [background-clip:padding-box,border-box,border-box] [background-origin:border-box] [border:calc(0.08*1rem)_solid_transparent] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50\",\n // before styles\n \"before:absolute before:bottom-[-20%] before:left-1/2 before:z-0 before:h-1/5 before:w-3/5 before:-translate-x-1/2 before:animate-rainbow before:bg-[linear-gradient(90deg,hsl(var(--color-1)),hsl(var(--color-5)),hsl(var(--color-3)),hsl(var(--color-4)),hsl(var(--color-2)))] before:[filter:blur(calc(0.8*1rem))]\",\n // light mode colors\n \"bg-[linear-gradient(#121213,#121213),linear-gradient(#121213_50%,rgba(18,18,19,0.6)_80%,rgba(18,18,19,0)),linear-gradient(90deg,hsl(var(--color-1)),hsl(var(--color-5)),hsl(var(--color-3)),hsl(var(--color-4)),hsl(var(--color-2)))]\",\n // dark mode colors\n \"dark:bg-[linear-gradient(#fff,#fff),linear-gradient(#fff_50%,rgba(255,255,255,0.6)_80%,rgba(0,0,0,0)),linear-gradient(90deg,hsl(var(--color-1)),hsl(var(--color-5)),hsl(var(--color-3)),hsl(var(--color-4)),hsl(var(--color-2)))]\",\n className,\n )}\n {...props}\n >\n {children}\n </button>\n );\n});\n\nRainbowButton.displayName = \"RainbowButton\";\n",
"type": "registry:ui",
"target": ""
}
Expand Down
2 changes: 1 addition & 1 deletion public/r/styles/default/ripple-button.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"files": [
{
"path": "magicui/ripple-button.tsx",
"content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport React, { MouseEvent, useEffect, useState } from \"react\";\n\ninterface RippleButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n rippleColor?: string;\n duration?: string;\n}\n\nconst RippleButton = React.forwardRef<HTMLButtonElement, RippleButtonProps>(\n (\n {\n className,\n children,\n rippleColor = \"#ffffff\",\n duration = \"600ms\",\n onClick,\n ...props\n },\n ref,\n ) => {\n const [buttonRipples, setButtonRipples] = useState<\n Array<{ x: number; y: number; size: number; key: number }>\n >([]);\n\n const handleClick = (event: MouseEvent<HTMLButtonElement>) => {\n createRipple(event);\n onClick?.(event);\n };\n\n const createRipple = (event: MouseEvent<HTMLButtonElement>) => {\n const button = event.currentTarget;\n const rect = button.getBoundingClientRect();\n const size = Math.max(rect.width, rect.height);\n const x = event.clientX - rect.left - size / 2;\n const y = event.clientY - rect.top - size / 2;\n\n const newRipple = { x, y, size, key: Date.now() };\n setButtonRipples((prevRipples) => [...prevRipples, newRipple]);\n };\n\n useEffect(() => {\n if (buttonRipples.length > 0) {\n const lastRipple = buttonRipples[buttonRipples.length - 1];\n const timeout = setTimeout(() => {\n setButtonRipples((prevRipples) =>\n prevRipples.filter((ripple) => ripple.key !== lastRipple.key),\n );\n }, parseInt(duration));\n return () => clearTimeout(timeout);\n }\n }, [buttonRipples, duration]);\n\n return (\n <button\n className={cn(\n \"relative flex cursor-pointer items-center justify-center overflow-hidden rounded-lg border-2 bg-background px-4 py-2 text-center text-primary\",\n className,\n )}\n onClick={handleClick}\n ref={ref}\n {...props}\n >\n <div className=\"relative z-10\">{children}</div>\n <span className=\"pointer-events-none absolute inset-0\">\n {buttonRipples.map((ripple) => (\n <span\n className=\"absolute animate-rippling rounded-full bg-background opacity-30\"\n key={ripple.key}\n style={{\n width: `${ripple.size}px`,\n height: `${ripple.size}px`,\n top: `${ripple.y}px`,\n left: `${ripple.x}px`,\n backgroundColor: rippleColor,\n transform: `scale(0)`,\n }}\n />\n ))}\n </span>\n </button>\n );\n },\n);\n\nRippleButton.displayName = \"RippleButton\";\n\nexport default RippleButton;\n",
"content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport React, { MouseEvent, useEffect, useState } from \"react\";\n\ninterface RippleButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n rippleColor?: string;\n duration?: string;\n}\n\nexport const RippleButton = React.forwardRef<\n HTMLButtonElement,\n RippleButtonProps\n>(\n (\n {\n className,\n children,\n rippleColor = \"#ffffff\",\n duration = \"600ms\",\n onClick,\n ...props\n },\n ref,\n ) => {\n const [buttonRipples, setButtonRipples] = useState<\n Array<{ x: number; y: number; size: number; key: number }>\n >([]);\n\n const handleClick = (event: MouseEvent<HTMLButtonElement>) => {\n createRipple(event);\n onClick?.(event);\n };\n\n const createRipple = (event: MouseEvent<HTMLButtonElement>) => {\n const button = event.currentTarget;\n const rect = button.getBoundingClientRect();\n const size = Math.max(rect.width, rect.height);\n const x = event.clientX - rect.left - size / 2;\n const y = event.clientY - rect.top - size / 2;\n\n const newRipple = { x, y, size, key: Date.now() };\n setButtonRipples((prevRipples) => [...prevRipples, newRipple]);\n };\n\n useEffect(() => {\n if (buttonRipples.length > 0) {\n const lastRipple = buttonRipples[buttonRipples.length - 1];\n const timeout = setTimeout(() => {\n setButtonRipples((prevRipples) =>\n prevRipples.filter((ripple) => ripple.key !== lastRipple.key),\n );\n }, parseInt(duration));\n return () => clearTimeout(timeout);\n }\n }, [buttonRipples, duration]);\n\n return (\n <button\n className={cn(\n \"relative flex cursor-pointer items-center justify-center overflow-hidden rounded-lg border-2 bg-background px-4 py-2 text-center text-primary\",\n className,\n )}\n onClick={handleClick}\n ref={ref}\n {...props}\n >\n <div className=\"relative z-10\">{children}</div>\n <span className=\"pointer-events-none absolute inset-0\">\n {buttonRipples.map((ripple) => (\n <span\n className=\"absolute animate-rippling rounded-full bg-background opacity-30\"\n key={ripple.key}\n style={{\n width: `${ripple.size}px`,\n height: `${ripple.size}px`,\n top: `${ripple.y}px`,\n left: `${ripple.x}px`,\n backgroundColor: rippleColor,\n transform: `scale(0)`,\n }}\n />\n ))}\n </span>\n </button>\n );\n },\n);\n\nRippleButton.displayName = \"RippleButton\";\n",
"type": "registry:ui",
"target": ""
}
Expand Down
Loading

0 comments on commit e0fbe78

Please sign in to comment.