shine-border

PreviousNext
Docs
aliimamcomponent

Preview

Loading preview…
registry/default/components/shine-border.tsx
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable react/no-unknown-property */
"use client"

import type React from "react"

type TColorProp = string | string[]

interface ShineBorderProps {
  borderRadius?: number
  borderWidth?: number
  duration?: number
  color?: TColorProp
  className?: string
  children: React.ReactNode
}

/**
 * ShineBorder
 * - Self-contained, no Tailwind, no external CSS.
 * - Uses an absolutely-positioned overlay with CSS masking to render an animated border.
 * - Customizable via props: borderRadius, borderWidth, duration, color (string or array).
 */
export function ShineBorder({
  borderRadius = 8,
  borderWidth = 1,
  duration = 14,
  color = [
    "#ff00ff", // neon pink
    "#00ffff", // neon cyan
    "#ff3131", // neon red
    "#00ff00", // neon green
    "#ffea00", // neon yellow
    "#ff6ec7", // hot magenta
    "#39ff14", // electric lime
    "#ff8300", // neon orange
    "#7df9ff", // electric blue
    "#fe019a", // neon fuchsia
  ],

  className,
  children,
}: ShineBorderProps) {
  const colorString =
    Array.isArray(color) && color.length > 0 ? color.join(",") : String(color)

  const rootStyle: React.CSSProperties = {
    // Expose variables for internal CSS
    // Note: These are referenced by the <style> block below
    // for animation, masking, and layout.
    // Using px ensures predictable rendering.
    // Consumers can still style the wrapper via className or style if desired.
    //@ts-ignore: CSS variables allowed at runtime
    "--sb-border-radius": `${borderRadius}px`,
    //@ts-ignore
    "--sb-border-width": `${borderWidth}px`,
    //@ts-ignore
    "--sb-duration": `${duration}s`,
  }

  const overlayStyle: React.CSSProperties = {
    position: "absolute",
    inset: 0,
    // padding defines the visible border thickness via mask
    padding: `var(--sb-border-width)`,
    borderRadius: `var(--sb-border-radius)`,
    backgroundImage: `radial-gradient(transparent, transparent, ${colorString}, transparent, transparent)`,
    backgroundSize: "300% 300%",
    animation: "sb-shine-pulse var(--sb-duration) linear infinite",
    // Show only the border region using masks (content-box vs border-box)
    // WebKit/Safari
    WebkitMask:
      "linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)",
    WebkitMaskComposite: "xor" as any,
    // Standard (Chromium/Firefox)
    mask: "linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)",
    maskComposite: "exclude" as any,
    pointerEvents: "none",
  }

  const containerStyle: React.CSSProperties = {
    position: "relative",
    display: "grid",
    placeItems: "center",
    width: "100%",
    height: "100%",
    borderRadius: `var(--sb-border-radius)`,
  }

  return (
    <div style={rootStyle} className={className}>
      {/* Local styles for the component (no external CSS or Tailwind needed) */}
      <style>{`
        @keyframes sb-shine-pulse {
          0%   { background-position: 0% 0%; }
          50%  { background-position: 100% 100%; }
          100% { background-position: 0% 0%; }
        }
      `}</style>

      <div style={containerStyle}>
        <div aria-hidden="true" style={overlayStyle} />
        {children}
      </div>
    </div>
  )
}

Installation

npx shadcn@latest add @aliimam/shine-border

Usage

import { ShineBorder } from "@/components/shine-border"
<ShineBorder />