This commit is contained in:
rgrgogu
2026-05-05 23:16:22 +08:00
parent 683b25956a
commit 745c51682e
125 changed files with 18925 additions and 2 deletions
@@ -0,0 +1,42 @@
import * as React from "react"
import { ArrowRight } from "lucide-react"
import { cn } from "@/lib/utils"
export function InteractiveHoverButton({
children,
className,
...props
}) {
// Split text and icons for consistent layout
const childArray = React.Children.toArray(children)
const hasIcon = childArray.length > 1
return (
<button
className={cn(
"group bg-background relative w-auto cursor-pointer overflow-hidden rounded-full border p-2 px-6 text-center font-medium transition-all disabled:pointer-events-none disabled:opacity-50",
className
)}
{...props}>
{/* Normal State */}
<div className="flex items-center justify-start gap-2">
<div className="bg-primary size-2 rounded-full transition-all duration-300 group-hover:scale-[300]" />
<span className="inline-flex items-center gap-2 transition-all duration-300 group-hover:translate-x-12 group-hover:opacity-0">
{childArray.map((child, index) => (
<React.Fragment key={index}>{child}</React.Fragment>
))}
</span>
</div>
{/* Hover State */}
<div className="text-primary-foreground absolute top-0 z-10 flex h-full w-full translate-x-12 items-center justify-center gap-2 opacity-0 transition-all duration-300 group-hover:-translate-x-5 group-hover:opacity-100">
<span className="inline-flex items-center gap-2">
{childArray.map((child, index) => (
<React.Fragment key={index}>{child}</React.Fragment>
))}
</span>
<ArrowRight className="size-4 shrink-0 xs:hidden lg:block" />
</div>
</button>
)
}
+60
View File
@@ -0,0 +1,60 @@
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
function Tabs({
className,
...props
}) {
return (
<TabsPrimitive.Root
data-slot="tabs"
className={cn("flex flex-col gap-2", className)}
{...props} />
);
}
function TabsList({
className,
...props
}) {
return (
<TabsPrimitive.List
data-slot="tabs-list"
className={cn(
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
className
)}
{...props} />
);
}
function TabsTrigger({
className,
...props
}) {
return (
<TabsPrimitive.Trigger
data-slot="tabs-trigger"
className={cn(
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props} />
);
}
function TabsContent({
className,
...props
}) {
return (
<TabsPrimitive.Content
data-slot="tabs-content"
className={cn("flex-1 outline-none", className)}
{...props} />
);
}
export { Tabs, TabsList, TabsTrigger, TabsContent }
+74
View File
@@ -0,0 +1,74 @@
import React, { useEffect, useState } from "react";
import { cn } from "@/lib/utils"
export const RippleButton = React.forwardRef((
{
className,
children,
rippleColor = "#ffffff",
duration = "600ms",
onClick,
...props
},
ref
) => {
const [buttonRipples, setButtonRipples] = useState([])
const handleClick = (event) => {
createRipple(event)
onClick?.(event)
}
const createRipple = (event) => {
const button = event.currentTarget
const rect = button.getBoundingClientRect()
const size = Math.max(rect.width, rect.height)
const x = event.clientX - rect.left - size / 2
const y = event.clientY - rect.top - size / 2
const newRipple = { x, y, size, key: Date.now() }
setButtonRipples((prevRipples) => [...prevRipples, newRipple])
}
useEffect(() => {
if (buttonRipples.length > 0) {
const lastRipple = buttonRipples[buttonRipples.length - 1]
const timeout = setTimeout(() => {
setButtonRipples((prevRipples) =>
prevRipples.filter((ripple) => ripple.key !== lastRipple.key))
}, parseInt(duration))
return () => clearTimeout(timeout);
}
}, [buttonRipples, duration])
return (
<button
className={cn(
"bg-background text-primary relative flex cursor-pointer items-center justify-center overflow-hidden rounded-lg border-2 px-4 py-2 text-center",
className
)}
onClick={handleClick}
ref={ref}
{...props}>
<div className="relative z-10">{children}</div>
<span className="pointer-events-none absolute inset-0">
{buttonRipples.map((ripple) => (
<span
className="animate-rippling bg-background absolute rounded-full opacity-30"
key={ripple.key}
style={{
width: `${ripple.size}px`,
height: `${ripple.size}px`,
top: `${ripple.y}px`,
left: `${ripple.x}px`,
backgroundColor: rippleColor,
transform: `scale(0)`,
}} />
))}
</span>
</button>
);
})
RippleButton.displayName = "RippleButton"