From ee3d3888436ccbd828f2a03c14f4e5f7919d9631 Mon Sep 17 00:00:00 2001 From: Dinil Thilakarathne <126305123+Dinil-Thilakarathne@users.noreply.github.com> Date: Thu, 5 Feb 2026 16:16:24 +0530 Subject: [PATCH] refactor: Embed component source code and installation target paths directly into registry JSON files and update the build script. --- public/r/accordion.json | 8 ++++++-- public/r/bubble-up-button.json | 4 +++- public/r/expandable-tabs.json | 4 +++- public/r/link-preview.json | 4 +++- public/r/marquee.json | 4 +++- public/r/registry.json | 30 ++++++++++++++++++++---------- public/r/ripple-button.json | 4 +++- public/r/spinning-text.json | 4 +++- public/r/stagger-text.json | 4 +++- public/r/vertical-tab.json | 4 +++- registry.json | 30 ++++++++++++++++++++---------- scripts/build-registry-json.ts | 3 ++- 12 files changed, 72 insertions(+), 31 deletions(-) diff --git a/public/r/accordion.json b/public/r/accordion.json index ed9320fa..21cd4697 100644 --- a/public/r/accordion.json +++ b/public/r/accordion.json @@ -6,11 +6,15 @@ "files": [ { "path": "registry/sonaui/accordion/accordion.tsx", - "type": "registry:ui" + "content": "\"use client\";\n\nimport {\n createContext,\n useContext,\n useEffect,\n useState,\n useRef,\n type ReactNode,\n} from \"react\";\nimport { motion } from \"motion/react\";\n\nimport { cn } from \"@/lib/utils\";\nimport AnimatedPlusMinusButton from \"./animated-plus-minus-button\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\n// Types\ntype AccordionVariant = \"default\" | \"outlined\" | \"splitted\";\n\ninterface AccordionProps {\n children: ReactNode;\n allowMultiple?: boolean;\n className?: string;\n variant?: AccordionVariant;\n}\n\nconst accordionWrapperVarinats = cva(\n \"flex flex-col overflow-clip rounded-2xl\",\n {\n variants: {\n variant: {\n default: \"overflow-clip rounded-2xl\",\n outlined: \"overflow-clip rounded-2xl\",\n splitted: \"overflow-clip rounded-2xl\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n },\n);\n\nconst accordionItemVariants = cva(\n \"relative overflow-hidden bg-background text-foreground\",\n {\n variants: {\n variant: {\n default: \"border-b border-border\",\n outlined:\n \"border-foreground border-t border-x last:border-b first:rounded-t-2xl last:rounded-b-2xl\",\n splitted: \"rounded-2xl \",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n },\n);\n\ninterface AccordionItemProps\n extends\n React.HTMLAttributes,\n VariantProps {\n children: ReactNode;\n className?: string;\n style?: React.CSSProperties;\n}\n\ninterface AccordionItemHeaderProps {\n value: string;\n children: ReactNode;\n}\ninterface AccordionItemTriggerProps {\n value: string;\n children: ReactNode;\n}\n\ninterface AccordionItemContentProps {\n children: ReactNode;\n value: string;\n}\n\nconst AccordionContext = createContext<{\n openItems: Set;\n toggleItem: (value: string) => void;\n variant: AccordionVariant;\n} | null>(null);\n\nconst AccordionRoot = ({\n children,\n allowMultiple = false,\n className,\n variant = \"default\",\n}: AccordionProps) => {\n const [openItems, setOpenItems] = useState>(new Set());\n const toggleItem = (value: string) => {\n setOpenItems((prev) => {\n const newOpenItems = new Set(prev);\n if (newOpenItems.has(value)) {\n newOpenItems.delete(value);\n } else {\n if (!allowMultiple) newOpenItems.clear();\n newOpenItems.add(value);\n }\n return newOpenItems;\n });\n };\n\n return (\n \n \n {children}\n \n \n );\n};\n\nconst AccordionItem = ({\n children,\n className,\n style,\n ...props\n}: AccordionItemProps) => {\n const context = useContext(AccordionContext);\n if (!context)\n throw new Error(\"AccordionItem must be used within AccordionRoot\");\n const { variant } = context;\n return (\n \n
{children}
\n \n );\n};\n\nconst AccordionItemHeader = ({ value, children }: AccordionItemHeaderProps) => {\n const context = useContext(AccordionContext);\n if (!context)\n throw new Error(\"AccordionTrigger must be used within AccordionRoot\");\n const { openItems } = context;\n\n const isOpen = openItems.has(value);\n\n return (\n
\n
{children}
\n \n
\n );\n};\n\nconst AccordionItemTrigger = ({\n value,\n children,\n}: AccordionItemTriggerProps) => {\n const context = useContext(AccordionContext);\n if (!context)\n throw new Error(\"AccordionTrigger must be used within AccordionRoot\");\n\n const { openItems, toggleItem } = context;\n const isOpen = openItems.has(value);\n\n return (\n toggleItem(value)}\n className=\"cursor-pointer\"\n >\n {children}\n \n );\n};\n\nconst AccordionItemContent = ({\n children,\n value,\n}: AccordionItemContentProps) => {\n const context = useContext(AccordionContext);\n if (!context)\n throw new Error(\"AccordionContent must be used within AccordionRoot\");\n\n const { openItems } = context;\n const isOpen = openItems.has(value);\n\n const ref = useRef(null);\n const [height, setHeight] = useState(0);\n\n useEffect(() => {\n if (ref.current) {\n setHeight(ref.current.offsetHeight + 16);\n }\n }, [isOpen]);\n\n const variants = {\n open: { opacity: [0, 0.5, 1], y: 0 },\n exit: { opacity: 0, y: 50 },\n initial: { opacity: 0, y: 50 },\n };\n\n return (\n \n \n {children}\n \n \n );\n};\n\nexport {\n AccordionRoot,\n AccordionItem,\n AccordionItemHeader,\n AccordionItemTrigger,\n AccordionItemContent,\n AccordionContext,\n};\n", + "type": "registry:ui", + "target": "components/sona-ui/accordion/accordion.tsx" }, { "path": "registry/sonaui/accordion/animated-plus-minus-button.tsx", - "type": "registry:ui" + "content": "\"use client\";\nimport { motion } from \"framer-motion\";\n\ninterface AnimatedPlusMinusButtonProps {\n size?: number;\n isOpen?: boolean;\n}\nconst AnimatedPlusMinusButton = ({\n size = 24,\n isOpen = false,\n}: AnimatedPlusMinusButtonProps) => {\n return (\n \n \n {/* Horizontal line */}\n \n\n {/* Vertical line */}\n \n \n \n );\n};\n\nexport default AnimatedPlusMinusButton;\n", + "type": "registry:ui", + "target": "components/sona-ui/accordion/animated-plus-minus-button.tsx" } ], "dependencies": [ diff --git a/public/r/bubble-up-button.json b/public/r/bubble-up-button.json index e9e07454..b52de49f 100644 --- a/public/r/bubble-up-button.json +++ b/public/r/bubble-up-button.json @@ -6,7 +6,9 @@ "files": [ { "path": "registry/sonaui/bubble-up-button/bubble-up-button.tsx", - "type": "registry:ui" + "content": "\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { motion, MotionConfigProps, useAnimation } from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\n\ninterface BubbleUpButtonProps\n extends React.ButtonHTMLAttributes {\n /** Content to display inside the button */\n children?: ReactNode;\n /** Motion configuration for animations */\n motionControls?: MotionConfigProps;\n /** Additional CSS classes */\n className?: string;\n}\nexport default function BubbleUpButton({\n children = \"Hover me!\",\n motionControls = {\n transition: { type: \"spring\", stiffness: 200, damping: 40 },\n },\n className = \"\",\n disabled = false,\n ...props\n}: BubbleUpButtonProps) {\n const controls = useAnimation();\n\n const handleMouseEnter = async () => {\n await controls.start({\n clipPath: \"ellipse(120% 120% at 50% 100%)\",\n });\n };\n\n const handleMouseLeave = async () => {\n await controls.start({\n clipPath: \"ellipse(120% 120% at 50% -120%)\",\n });\n controls.set({ clipPath: \"ellipse(0% 0% at 50% 100%)\" });\n };\n\n return (\n \n \n \n {children}\n \n \n );\n}\n", + "type": "registry:ui", + "target": "components/sona-ui/bubble-up-button/bubble-up-button.tsx" } ], "dependencies": [], diff --git a/public/r/expandable-tabs.json b/public/r/expandable-tabs.json index 6d7be943..53d14119 100644 --- a/public/r/expandable-tabs.json +++ b/public/r/expandable-tabs.json @@ -6,7 +6,9 @@ "files": [ { "path": "registry/sonaui/expandable-tabs/expandable-tabs.tsx", - "type": "registry:ui" + "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { motion, MotionConfig, type MotionConfigProps } from \"motion/react\";\nimport { IconType } from \"react-icons\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport type TabsData = {\n title: string;\n icon: IconType;\n};\n\ninterface ExpandableTabsProps {\n tabs: TabsData[];\n containerClassName?: string;\n defaultActiveIndex?: number;\n motionConfig?: MotionConfigProps;\n}\n\nexport default function ExpandableTabs({\n tabs,\n containerClassName = \"\",\n defaultActiveIndex = 0,\n motionConfig = {\n transition: { duration: 0.2, ease: \"easeInOut\" },\n },\n}: ExpandableTabsProps) {\n const [isActive, setIsActive] = useState(defaultActiveIndex);\n\n return (\n \n \n {tabs.map((tab, index) => (\n setIsActive(index)}\n tabIndex={0}\n >\n
\n \n
\n \n {tab.title}\n \n \n ))}\n
\n \n );\n}\n\nconst TabItemVariants = {\n active: {\n opacity: 1,\n width: \"auto\",\n y: 0,\n },\n inactive: {\n opacity: 0,\n width: 0,\n y: 20,\n },\n};\n", + "type": "registry:ui", + "target": "components/sona-ui/expandable-tabs/expandable-tabs.tsx" } ], "dependencies": [ diff --git a/public/r/link-preview.json b/public/r/link-preview.json index 26e63bdb..a95b374f 100644 --- a/public/r/link-preview.json +++ b/public/r/link-preview.json @@ -6,7 +6,9 @@ "files": [ { "path": "registry/sonaui/link-preview/link-preview.tsx", - "type": "registry:ui" + "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport Link from \"next/link\";\n\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { FaArrowUpRightFromSquare } from \"react-icons/fa6\";\nimport useMeasure from \"react-use-measure\";\nimport { useMediaQuery } from \"@/hooks/useMediaQuery\";\n\ninterface LinkPreviewProps extends React.HTMLAttributes {\n link: string;\n text: string;\n showIcon?: boolean;\n}\n\nexport default function LinkPreview({\n link,\n text,\n showIcon = true,\n ...linkProps\n}: LinkPreviewProps) {\n const [previewRef, previewBounds] = useMeasure();\n const [containerRef, containerBounds] = useMeasure();\n const desktop = useMediaQuery(\"(min-width: 768px)\");\n\n const [isHover, setIsHover] = useState(false);\n\n return (\n <>\n {\n if (!desktop) return;\n e.preventDefault();\n setIsHover(true);\n }}\n onClick={(e) => {\n if (!desktop) return;\n e.preventDefault();\n setIsHover((prev) => !prev);\n }}\n onMouseLeave={() => {\n if (isHover) {\n setIsHover(false);\n }\n }}\n onFocus={(e) => {\n e.preventDefault();\n setIsHover(true);\n }}\n onBlur={() => setIsHover(false)}\n ref={containerRef}\n >\n {text}\n {showIcon && (\n \n \n \n )}\n \n \n {isHover && desktop && (\n setIsHover(true)}\n onMouseLeave={() => setIsHover(false)}\n >\n \n
\n External Link\n \n \n \n
\n \n {link}\n \n
\n \n )}\n
\n \n );\n}\n", + "type": "registry:ui", + "target": "components/sona-ui/link-preview/link-preview.tsx" } ], "dependencies": [ diff --git a/public/r/marquee.json b/public/r/marquee.json index 9631b11c..1546ed8c 100644 --- a/public/r/marquee.json +++ b/public/r/marquee.json @@ -6,7 +6,9 @@ "files": [ { "path": "registry/sonaui/marquee/marquee.tsx", - "type": "registry:ui" + "content": "\"use client\";\n\nimport { useClock } from \"@/hooks/useClock\";\nimport { cn } from \"@/lib/utils\";\nimport {\n motion,\n useMotionTemplate,\n useScroll,\n useSpring,\n useTransform,\n useVelocity,\n} from \"motion/react\";\nimport { forwardRef, useEffect, useRef, useState } from \"react\";\nimport useMeasure from \"react-use-measure\";\n\n// Constants\nconst DEFAULT_DURATION = 10; // Default duration in seconds\n\n// Helper function to calculate the number of marquee items\nconst calculateItemCount = (\n containerWidth: number,\n itemWidth: number,\n): number => {\n return Math.ceil(containerWidth / itemWidth);\n};\n\ninterface MarqueeProps {\n children: React.ReactNode;\n className?: string;\n containerClassName?: string;\n duration?: number;\n reverse?: boolean;\n activeScroll?: boolean;\n activeHover?: boolean;\n}\n\nexport default function Marquee({\n children,\n className,\n containerClassName,\n duration = DEFAULT_DURATION,\n reverse = false,\n activeScroll = false,\n activeHover = false,\n}: MarqueeProps) {\n const ref = useRef(null);\n const [parentBounds, setParentBounds] = useState({ width: 0, height: 0 });\n const [marqueeItemRef, marqueeItemBounds] = useMeasure();\n const [count, setCount] = useState(0);\n const [activeDirection, setActiveDirection] = useState(false);\n const [speed, setSpeed] = useState(1);\n const [isHovered, setIsHovered] = useState(false);\n\n const [isClient, setIsClient] = useState(false);\n\n useEffect(() => {\n setIsClient(true);\n }, []);\n\n const { scrollY } = useScroll();\n const scrollVelocity = useVelocity(scrollY);\n const smoothVelocity = useSpring(scrollVelocity, {\n damping: 50,\n stiffness: 400,\n });\n const velocityFactor = useTransform(smoothVelocity, [0, 1000], [0, 5], {\n clamp: false,\n });\n\n useEffect(() => {\n if (ref.current && isClient) {\n if (ref.current.parentElement) {\n const { width, height } =\n ref.current.parentElement.getBoundingClientRect();\n setParentBounds({ width, height });\n }\n }\n }, [ref, isClient]);\n\n // Update item count when dimensions change\n useEffect(() => {\n if (marqueeItemBounds.width > 0 && parentBounds.width > 0) {\n setCount(calculateItemCount(parentBounds.width, marqueeItemBounds.width));\n }\n }, [marqueeItemBounds, parentBounds, children]);\n\n // Update active direction based on velocity\n useEffect(() => {\n return velocityFactor.on(\"change\", (value) => {\n setActiveDirection(value < 0);\n });\n }, [velocityFactor]);\n\n // Update speed dynamically based on velocity factor\n useEffect(() => {\n const unsubscribe = velocityFactor.on(\"change\", (value) => {\n setSpeed(activeScroll && value > 0 ? value : 1);\n });\n\n return () => unsubscribe();\n }, [velocityFactor, activeScroll]);\n\n // Update speed when hovered\n useEffect(() => {\n if (!activeHover) return;\n if (isHovered) {\n setSpeed(0);\n } else {\n setSpeed(activeScroll ? velocityFactor.get() : 1);\n }\n }, [isHovered, activeScroll, velocityFactor, activeHover]);\n\n const clock = useClock({\n defaultValue: Date.now(),\n reverse: activeScroll ? activeDirection : reverse,\n speed,\n }).value;\n\n const progress = useTransform(clock, (time) => (time % duration) / duration);\n const percentage = useTransform(progress, (t) => t * 100);\n const translateX = useMotionTemplate`-${percentage}%`;\n\n return (\n isClient && (\n setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n \n {children}\n \n {Array.from({ length: count }).map((_, i) => (\n \n {children}\n \n ))}\n \n )\n );\n}\n\ninterface MarqueeItemProps {\n children: React.ReactNode;\n style?: React.CSSProperties;\n className?: string;\n isCopy?: boolean;\n}\n\nconst MarqueeItem = forwardRef(\n ({ children, className, isCopy, style }, ref) => {\n return (\n \n {children}\n \n );\n },\n);\n\nMarqueeItem.displayName = \"MarqueeItem\";\n", + "type": "registry:ui", + "target": "components/sona-ui/marquee/marquee.tsx" } ], "dependencies": [ diff --git a/public/r/registry.json b/public/r/registry.json index 42d50e45..55ff01f8 100644 --- a/public/r/registry.json +++ b/public/r/registry.json @@ -26,11 +26,13 @@ "files": [ { "path": "registry/sonaui/accordion/accordion.tsx", - "type": "registry:ui" + "type": "registry:ui", + "target": "components/sona-ui/accordion/accordion.tsx" }, { "path": "registry/sonaui/accordion/animated-plus-minus-button.tsx", - "type": "registry:ui" + "type": "registry:ui", + "target": "components/sona-ui/accordion/animated-plus-minus-button.tsx" } ], "dependencies": [ @@ -47,7 +49,8 @@ "files": [ { "path": "registry/sonaui/spinning-text/spinning-text.tsx", - "type": "registry:ui" + "type": "registry:ui", + "target": "components/sona-ui/spinning-text/spinning-text.tsx" } ], "dependencies": [], @@ -61,7 +64,8 @@ "files": [ { "path": "registry/sonaui/bubble-up-button/bubble-up-button.tsx", - "type": "registry:ui" + "type": "registry:ui", + "target": "components/sona-ui/bubble-up-button/bubble-up-button.tsx" } ], "dependencies": [], @@ -75,7 +79,8 @@ "files": [ { "path": "registry/sonaui/marquee/marquee.tsx", - "type": "registry:ui" + "type": "registry:ui", + "target": "components/sona-ui/marquee/marquee.tsx" } ], "dependencies": [ @@ -91,7 +96,8 @@ "files": [ { "path": "registry/sonaui/ripple-button/ripple-button.tsx", - "type": "registry:ui" + "type": "registry:ui", + "target": "components/sona-ui/ripple-button/ripple-button.tsx" } ], "dependencies": [], @@ -105,7 +111,8 @@ "files": [ { "path": "registry/sonaui/vertical-tab/vertical-tab.tsx", - "type": "registry:ui" + "type": "registry:ui", + "target": "components/sona-ui/vertical-tab/vertical-tab.tsx" } ], "dependencies": [ @@ -121,7 +128,8 @@ "files": [ { "path": "registry/sonaui/expandable-tabs/expandable-tabs.tsx", - "type": "registry:ui" + "type": "registry:ui", + "target": "components/sona-ui/expandable-tabs/expandable-tabs.tsx" } ], "dependencies": [ @@ -137,7 +145,8 @@ "files": [ { "path": "registry/sonaui/link-preview/link-preview.tsx", - "type": "registry:ui" + "type": "registry:ui", + "target": "components/sona-ui/link-preview/link-preview.tsx" } ], "dependencies": [ @@ -153,7 +162,8 @@ "files": [ { "path": "registry/sonaui/stagger-text/stagger-text.tsx", - "type": "registry:ui" + "type": "registry:ui", + "target": "components/sona-ui/stagger-text/stagger-text.tsx" } ], "dependencies": [], diff --git a/public/r/ripple-button.json b/public/r/ripple-button.json index 4e2b5a7f..ad4e8269 100644 --- a/public/r/ripple-button.json +++ b/public/r/ripple-button.json @@ -6,7 +6,9 @@ "files": [ { "path": "registry/sonaui/ripple-button/ripple-button.tsx", - "type": "registry:ui" + "content": "\"use client\";\n\nimport { useState, useCallback, useMemo, type ReactNode } from \"react\";\nimport { motion } from \"motion/react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport interface RippleButtonProps\n extends React.ButtonHTMLAttributes {\n children: ReactNode;\n scaleAmount?: number;\n className?: string;\n duration?: number;\n rippleStyle?: string;\n}\n\ninterface RippleProps {\n x: number;\n y: number;\n key: number;\n}\n\nexport default function RippleButton({\n children,\n className,\n scaleAmount = 25,\n duration = 0.5,\n rippleStyle,\n ...props\n}: RippleButtonProps) {\n const [ripple, setRipple] = useState(null);\n const [isHover, setIsHover] = useState(false);\n\n const handleMouseEnter = useCallback(\n (event: React.MouseEvent) => {\n const button = event.currentTarget;\n const rect = button.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n setRipple({ x, y, key: Date.now() });\n },\n [],\n );\n\n const buttonClasses = useMemo(\n () =>\n cn(\n \"relative overflow-hidden rounded-full border border-border bg-background px-4 py-2 leading-[16px] transition-all duration-300 ease-in-out hover:cursor-pointer\",\n className,\n ),\n [className],\n );\n\n return (\n setIsHover(true)}\n onMouseLeave={() => setIsHover(false)}\n {...props}\n >\n {ripple && (\n \n )}\n {children}\n \n );\n}\n\nexport interface RippleButtonTextProps\n extends React.HTMLAttributes {\n text: string;\n className?: string;\n}\n\nexport function RippleButtonText({ text, className }: RippleButtonTextProps) {\n return (\n \n {text}\n \n );\n}\n", + "type": "registry:ui", + "target": "components/sona-ui/ripple-button/ripple-button.tsx" } ], "dependencies": [], diff --git a/public/r/spinning-text.json b/public/r/spinning-text.json index 2015506a..c5fa87fa 100644 --- a/public/r/spinning-text.json +++ b/public/r/spinning-text.json @@ -6,7 +6,9 @@ "files": [ { "path": "registry/sonaui/spinning-text/spinning-text.tsx", - "type": "registry:ui" + "content": "\"use client\";\n\nimport { CSSProperties, useMemo } from \"react\";\nimport { motion, type Transition, Variants } from \"motion/react\";\n\nimport { cn } from \"@/lib/utils\";\n\ntype SpinningTextProps = {\n children: string | string[];\n style?: CSSProperties;\n duration?: number;\n className?: string;\n reverse?: boolean;\n radius?: number;\n transition?: Transition;\n variants?: {\n container?: Variants;\n item?: Variants;\n };\n};\n\nconst BASE_TRANSITION = {\n repeat: Infinity,\n ease: \"linear\" as const,\n};\n\nconst BASE_ITEM_VARIANTS = {\n hidden: {\n opacity: 1,\n },\n visible: {\n opacity: 1,\n },\n};\n\nexport default function SpinningText({\n children,\n duration = 10,\n style,\n className,\n reverse = false,\n radius = 5,\n transition,\n variants,\n}: SpinningTextProps) {\n if (typeof children !== \"string\" && !Array.isArray(children)) {\n throw new Error(\"children must be a string or an array of strings\");\n }\n\n const textContent = useMemo(() => {\n if (Array.isArray(children)) {\n if (!children.every((child) => typeof child === \"string\")) {\n throw new Error(\"all elements in children array must be strings\");\n }\n return children.join(\"\");\n }\n return children;\n }, [children]);\n\n const characters = useMemo(() => {\n const chars = textContent.split(\"\");\n chars.push(\" \");\n return chars;\n }, [textContent]);\n\n const finalTransition = useMemo(\n () => ({\n ...BASE_TRANSITION,\n ...transition,\n duration: (transition as { duration?: number })?.duration ?? duration,\n }),\n [transition, duration],\n );\n\n const containerVariants = useMemo(\n () => ({\n visible: { rotate: reverse ? -360 : 360 },\n ...variants?.container,\n }),\n [reverse, variants],\n );\n\n const itemVariants = useMemo(\n () => ({\n ...BASE_ITEM_VARIANTS,\n ...variants?.item,\n }),\n [variants],\n );\n\n return (\n \n {characters.map((char, index) => (\n \n {char}\n \n ))}\n {textContent}\n \n );\n}\n", + "type": "registry:ui", + "target": "components/sona-ui/spinning-text/spinning-text.tsx" } ], "dependencies": [], diff --git a/public/r/stagger-text.json b/public/r/stagger-text.json index f5b1b08b..5858e441 100644 --- a/public/r/stagger-text.json +++ b/public/r/stagger-text.json @@ -6,7 +6,9 @@ "files": [ { "path": "registry/sonaui/stagger-text/stagger-text.tsx", - "type": "registry:ui" + "content": "\"use client\";\n\nimport { motion, type Variants } from \"motion/react\";\nimport { useState, type ReactNode } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\ntype StaggerTextEleType = \"span\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\";\n\ntype StaggerTextProps =\n React.ComponentPropsWithoutRef & {\n text: string;\n className?: string;\n as?: T;\n };\n\nexport default function StaggerText({\n text = \"text\",\n className,\n as = \"h3\",\n}: StaggerTextProps) {\n const Tag = as; // Explicitly type as a React component\n const [activeIndex, setActiveIndex] = useState(5);\n const [isActive, setIsActive] = useState(false);\n return (\n <>\n

\n {text}\n

\n {\n e.preventDefault();\n e.clipboardData.setData(\"text/plain\", text);\n }}\n >\n {text.split(\"\").map((char, i) => {\n const delay = Math.abs(activeIndex - i);\n return (\n {\n setActiveIndex(i);\n setIsActive(true);\n }}\n onMouseLeave={() => {\n setActiveIndex(-1);\n setIsActive(false);\n }}\n delay={delay}\n isHovered={isActive}\n />\n );\n })}\n \n \n );\n}\n\ninterface StaggerTextItemProps\n extends React.ComponentPropsWithoutRef {\n char: string;\n delay?: number;\n isHovered?: boolean;\n}\n\nconst StaggerTextItem = ({\n char,\n delay,\n isHovered,\n ...props\n}: StaggerTextItemProps) => {\n return (\n \n \n {char}\n \n \n {char}\n \n \n );\n};\n\ninterface StaggerItemSegmentProps\n extends React.ComponentPropsWithoutRef {\n children: ReactNode;\n isCopy?: boolean;\n}\n\nconst StaggerItemSegment = ({\n children,\n isCopy,\n ...props\n}: StaggerItemSegmentProps) => {\n const content = children === \" \" ? \"\\u00A0\" : children;\n\n if (isCopy) {\n return (\n \n {content}\n \n );\n }\n\n return (\n \n {content}\n \n );\n};\n\nconst variants: Variants = {\n initial: { y: 0 },\n animate: (i: number) => ({\n y: \"-100%\",\n transition: {\n delay: i * 0.04,\n duration: 0.4,\n ease: \"easeInOut\",\n type: \"tween\" as const,\n },\n }),\n exit: (i: number) => ({\n y: 0,\n transition: { delay: i * 0.02, duration: 0.3 },\n }),\n};\n", + "type": "registry:ui", + "target": "components/sona-ui/stagger-text/stagger-text.tsx" } ], "dependencies": [], diff --git a/public/r/vertical-tab.json b/public/r/vertical-tab.json index 1cb70f13..205424db 100644 --- a/public/r/vertical-tab.json +++ b/public/r/vertical-tab.json @@ -6,7 +6,9 @@ "files": [ { "path": "registry/sonaui/vertical-tab/vertical-tab.tsx", - "type": "registry:ui" + "content": "\"use client\";\n\nimport { useRef, useState } from \"react\";\nimport { motion } from \"motion/react\";\nimport useMeasure from \"react-use-measure\";\n\nimport { cn } from \"@/lib/utils\";\n\ninterface VerticalTabProps {\n tabs: {\n title: string;\n }[];\n indicatorBgColor?: string;\n activeTabBgColor?: string;\n containerClassName?: string;\n}\n\nexport default function VerticalTab({\n tabs,\n indicatorBgColor = \"bg-slate-300\",\n activeTabBgColor = \"bg-slate-400 dark:bg-slate-600\",\n containerClassName = \"\",\n}: VerticalTabProps) {\n const [activeIndex, setActiveIndex] = useState(0);\n const [isMouseOver, setIsMouseOver] = useState(false);\n const [lastHoveredIndex, setLastHoveredIndex] = useState(null);\n\n const [ref, bounds] = useMeasure();\n const tabRef = useRef(null);\n\n const handleMouseEnter = (tabElement: HTMLDivElement, index: number) => {\n tabRef.current = tabElement;\n ref(tabElement); // Update the ref dynamically\n setIsMouseOver(true);\n setLastHoveredIndex(index); // Store the last hovered index\n };\n\n const handleMouseLeave = () => {\n tabRef.current = null;\n setIsMouseOver(false);\n ref(null); // Reset the ref when mouse leaves\n };\n\n const handleMouseClick = (i: number) => {\n setActiveIndex(i);\n };\n const handleKeyDown = (\n e: React.KeyboardEvent,\n currentIndex: number,\n ) => {\n if (e.key === \"ArrowRight\") {\n setActiveIndex((prev) => (prev + 1) % tabs.length);\n } else if (e.key === \"ArrowLeft\") {\n setActiveIndex((prev) => (prev - 1 + tabs.length) % tabs.length);\n }\n if (e.key === \"Enter\" || e.key === \" \") {\n setActiveIndex(currentIndex);\n }\n };\n\n return (\n handleMouseLeave()}\n >\n {isMouseOver && tabRef.current && (\n \n )}\n
\n {tabs.map((tab, index) => (\n handleMouseEnter(e.currentTarget, index)}\n onKeyDown={(e) => handleKeyDown(e, index)}\n onClick={() => handleMouseClick(index)}\n aria-selected={index === activeIndex}\n aria-label={`Tab ${index + 1}`}\n />\n ))}\n
\n \n );\n}\n\ninterface TabItemProps extends React.HTMLAttributes {\n title: string;\n}\n\nconst TabItem = ({ title, ...props }: TabItemProps) => {\n return (\n
\n {title}\n
\n );\n};\n", + "type": "registry:ui", + "target": "components/sona-ui/vertical-tab/vertical-tab.tsx" } ], "dependencies": [ diff --git a/registry.json b/registry.json index 42d50e45..55ff01f8 100644 --- a/registry.json +++ b/registry.json @@ -26,11 +26,13 @@ "files": [ { "path": "registry/sonaui/accordion/accordion.tsx", - "type": "registry:ui" + "type": "registry:ui", + "target": "components/sona-ui/accordion/accordion.tsx" }, { "path": "registry/sonaui/accordion/animated-plus-minus-button.tsx", - "type": "registry:ui" + "type": "registry:ui", + "target": "components/sona-ui/accordion/animated-plus-minus-button.tsx" } ], "dependencies": [ @@ -47,7 +49,8 @@ "files": [ { "path": "registry/sonaui/spinning-text/spinning-text.tsx", - "type": "registry:ui" + "type": "registry:ui", + "target": "components/sona-ui/spinning-text/spinning-text.tsx" } ], "dependencies": [], @@ -61,7 +64,8 @@ "files": [ { "path": "registry/sonaui/bubble-up-button/bubble-up-button.tsx", - "type": "registry:ui" + "type": "registry:ui", + "target": "components/sona-ui/bubble-up-button/bubble-up-button.tsx" } ], "dependencies": [], @@ -75,7 +79,8 @@ "files": [ { "path": "registry/sonaui/marquee/marquee.tsx", - "type": "registry:ui" + "type": "registry:ui", + "target": "components/sona-ui/marquee/marquee.tsx" } ], "dependencies": [ @@ -91,7 +96,8 @@ "files": [ { "path": "registry/sonaui/ripple-button/ripple-button.tsx", - "type": "registry:ui" + "type": "registry:ui", + "target": "components/sona-ui/ripple-button/ripple-button.tsx" } ], "dependencies": [], @@ -105,7 +111,8 @@ "files": [ { "path": "registry/sonaui/vertical-tab/vertical-tab.tsx", - "type": "registry:ui" + "type": "registry:ui", + "target": "components/sona-ui/vertical-tab/vertical-tab.tsx" } ], "dependencies": [ @@ -121,7 +128,8 @@ "files": [ { "path": "registry/sonaui/expandable-tabs/expandable-tabs.tsx", - "type": "registry:ui" + "type": "registry:ui", + "target": "components/sona-ui/expandable-tabs/expandable-tabs.tsx" } ], "dependencies": [ @@ -137,7 +145,8 @@ "files": [ { "path": "registry/sonaui/link-preview/link-preview.tsx", - "type": "registry:ui" + "type": "registry:ui", + "target": "components/sona-ui/link-preview/link-preview.tsx" } ], "dependencies": [ @@ -153,7 +162,8 @@ "files": [ { "path": "registry/sonaui/stagger-text/stagger-text.tsx", - "type": "registry:ui" + "type": "registry:ui", + "target": "components/sona-ui/stagger-text/stagger-text.tsx" } ], "dependencies": [], diff --git a/scripts/build-registry-json.ts b/scripts/build-registry-json.ts index 28c89d51..bc896ba2 100644 --- a/scripts/build-registry-json.ts +++ b/scripts/build-registry-json.ts @@ -82,6 +82,7 @@ async function buildRegistryJson() { // Merge with metadata const metadata = metadataMap[componentName] || {}; + const { files: metadataFiles, ...restMetadata } = metadata; const entry = { name: componentName, @@ -91,7 +92,7 @@ async function buildRegistryJson() { files: componentFiles, dependencies: metadata.dependencies || [], registryDependencies: metadata.registryDependencies || [], - ...metadata, // overwrite with specific metadata if exists + ...restMetadata, // overwrite with specific metadata if exists }; // Write individual component JSON to public/r/[name].json