===== COMPONENT: animated-badge ===== Title: animated-badge Description: An animated badge component. --- file: eldoraui/animated-badge.tsx --- "use client" import Link from "next/link" import { ChevronRight } from "lucide-react" import { motion } from "motion/react" type AnimatedBadgeProps = { text?: string color?: string // hex or css color value href?: string // optional redirect link } function hexToRgba(hexColor: string, alpha: number): string { const hex = hexColor.replace("#", "") if (hex.length === 3) { const r = parseInt(hex[0] + hex[0], 16) const g = parseInt(hex[1] + hex[1], 16) const b = parseInt(hex[2] + hex[2], 16) return `rgba(${r}, ${g}, ${b}, ${alpha})` } if (hex.length === 6) { const r = parseInt(hex.substring(0, 2), 16) const g = parseInt(hex.substring(2, 4), 16) const b = parseInt(hex.substring(4, 6), 16) return `rgba(${r}, ${g}, ${b}, ${alpha})` } return hexColor } const AnimatedBadge = ({ text = "Introducing Eldoraui", color = "#22d3ee", href, }: AnimatedBadgeProps) => { const content = (
{/* */}
{text} ) return ( <> {href ? ( {content} ) : ( content )} ) } export default AnimatedBadge ===== EXAMPLE: animated-badge-demo ===== Title: animated-badge-demo --- file: example/animated-badge-demo.tsx --- import AnimatedBadge from "@/registry/eldoraui/animated-badge" export function AnimatedBadgeDemo() { return (
) } ===== COMPONENT: animated-frameworks ===== Title: animated-frameworks Description: An animated frameworks component. --- file: eldoraui/animated-frameworks.tsx --- "use client" import { useEffect, useState } from "react" import { cn } from "@/lib/utils" import { Icons } from "@/components/icons" type AnimatedFrameworksProps = { cardTitle?: string cardDescription?: string } const AnimatedFrameworks = ({ cardTitle = "Universal Compatibility", cardDescription = "Works seamlessly with Next.js, React, HTML, Apple, GitHub, OpenAI, and more fits everywhere.", }: AnimatedFrameworksProps) => { return (
{cardTitle}
{cardDescription}
) } export default AnimatedFrameworks const FrameworkCard = () => { const [nextJsTransform, setNextJsTransform] = useState("none") const [reactTransform, setReactTransform] = useState("none") const [htmlTransform, setHtmlTransform] = useState("none") useEffect(() => { const cycleAnimations = async () => { const upStyle = "translateY(-3.71px) rotateX(10.71deg) translateZ(20px)" const downStyle = "none" const transitionDuration = 1100 const durationOfUpState = 1200 const delayBetweenCards = 600 while (true) { setReactTransform(upStyle) await new Promise((resolve) => setTimeout(resolve, durationOfUpState)) setReactTransform(downStyle) await new Promise((resolve) => setTimeout(resolve, transitionDuration + delayBetweenCards) ) setNextJsTransform(upStyle) await new Promise((resolve) => setTimeout(resolve, durationOfUpState)) setNextJsTransform(downStyle) await new Promise((resolve) => setTimeout(resolve, transitionDuration + delayBetweenCards) ) setHtmlTransform(upStyle) await new Promise((resolve) => setTimeout(resolve, durationOfUpState)) setHtmlTransform(downStyle) await new Promise((resolve) => setTimeout(resolve, transitionDuration + delayBetweenCards) ) } } cycleAnimations() }, []) const cardClasses = "flex aspect-square items-center justify-center rounded-md border bg-gradient-to-b from-neutral-50 to-neutral-100 p-4 " + "dark:border-neutral-800 dark:from-[#272727] dark:to-[#3d3d3d] " + "border-neutral-300 shadow-[0_8px_30px_rgb(0,0,0,0.12)] " + "[@media(min-width:320px)]:h-20 [@media(min-width:500px)]:h-36 " + "transition-transform duration-1000 ease-out will-change-transform" return ( <>
) } ===== EXAMPLE: animated-frameworks-demo ===== Title: animated-frameworks-demo --- file: example/animated-frameworks-demo.tsx --- import AnimatedFrameworks from "@/registry/eldoraui/animated-frameworks" export function AnimatedFrameworksExample() { return ( ) } ===== COMPONENT: animated-grid-pattern ===== Title: animated-grid-pattern Description: An animated grid pattern component. --- file: eldoraui/animated-grid-pattern.tsx --- "use client" import React, { useEffect, useMemo, useRef, useState } from "react" import { Canvas, useFrame, useThree } from "@react-three/fiber" import { useTheme } from "next-themes" import { ExtrudeGeometry, Shape } from "three" import * as THREE from "three" interface GridProps { position: [number, number, number] width?: number length?: number cornerRadius?: number gridPosition: [number, number] hoveredGrid: [number, number] | null rippleScale?: number rippleRadius?: number } const Grid = ({ position, width = 4, length = 4, cornerRadius = 2, gridPosition, hoveredGrid, rippleScale = 0.3, rippleRadius = 3, }: GridProps) => { const meshRef = useRef(null) const [currentScale, setCurrentScale] = useState(1) const { theme } = useTheme() const gridColor = theme === "dark" ? "#232323" : "#e5e7eb" const geometry = useMemo(() => { const shape = new Shape() const angleStep = Math.PI * 0.5 const radius = cornerRadius const halfWidth = width / 2 const halfLength = length / 2 shape.absarc( halfWidth - radius, halfLength - radius, radius, angleStep * 0, angleStep * 1 ) shape.absarc( -halfWidth + radius, halfLength - radius, radius, angleStep * 1, angleStep * 2 ) shape.absarc( -halfWidth + radius, -halfLength + radius, radius, angleStep * 2, angleStep * 3 ) shape.absarc( halfWidth - radius, -halfLength + radius, radius, angleStep * 3, angleStep * 4 ) const extrudeSettings = { depth: 0.3, bevelEnabled: true, bevelThickness: 0.05, bevelSize: 0.05, bevelSegments: 20, curveSegments: 20, } const geometry = new ExtrudeGeometry(shape, extrudeSettings) geometry.center() return geometry }, [width, length, cornerRadius]) useEffect(() => { return () => { geometry.dispose() } }, [geometry]) useFrame(() => { if (meshRef.current) { let targetScale = 1 const isThisGridHovered = hoveredGrid && gridPosition[0] === hoveredGrid[0] && gridPosition[1] === hoveredGrid[1] if (isThisGridHovered) { targetScale = 5 } else if (hoveredGrid) { const dx = gridPosition[0] - hoveredGrid[0] const dz = gridPosition[1] - hoveredGrid[1] const distance = Math.sqrt(dx * dx + dz * dz) if (distance <= rippleRadius && distance > 0) { const falloff = Math.max(0, 1 - distance / rippleRadius) const rippleEffect = falloff * rippleScale targetScale = 1 + rippleEffect * 3 } } const lerpFactor = 0.1 const newScale = currentScale + (targetScale - currentScale) * lerpFactor setCurrentScale(newScale) meshRef.current.scale.z = newScale } }) useEffect(() => { if (meshRef.current) { meshRef.current.userData.gridPosition = gridPosition } }, [gridPosition]) return ( ) } function HoverDetector({ onHoverChange, }: { gridSize: number spacingX: number spacingZ: number onHoverChange: (hoveredGrid: [number, number] | null) => void }) { const { camera, raycaster, pointer, scene } = useThree() useFrame(() => { raycaster.setFromCamera(pointer, camera) const intersects = raycaster.intersectObjects(scene.children, true) if (intersects.length > 0) { for (const intersect of intersects) { const mesh = intersect.object if (mesh.userData && mesh.userData.gridPosition) { const gridPos = mesh.userData.gridPosition as [number, number] onHoverChange(gridPos) return } } } onHoverChange(null) }) return null } interface GridOfGridesProps { gridSize: number gridWidth: number gridLength: number gap: number rippleScale: number rippleRadius: number cornerRadius: number } function GridOfGrides({ gridSize, gridWidth, gridLength, gap, rippleScale, rippleRadius, cornerRadius, }: GridOfGridesProps) { const spacingX = gridWidth + gap const spacingZ = gridLength + gap const [hoveredGrid, setHoveredGrid] = useState<[number, number] | null>(null) const Grides = [] for (let x = 0; x < gridSize; x++) { for (let z = 0; z < gridSize; z++) { const posX = (x - (gridSize - 1) / 2) * spacingX const posZ = (z - (gridSize - 1) / 2) * spacingZ Grides.push( ) } } return ( <> {Grides} ) } interface AnimatedGridPatternProps { gridSize?: number gridWidth?: number gridLength?: number gap?: number rippleScale?: number rippleRadius?: number cornerRadius?: number cameraPosition?: [number, number, number] cameraRotation?: [number, number, number] fov?: number } export function AnimatedGridPattern({ gridSize = 10, gridWidth = 4, gridLength = 4, gap = 0.05, rippleScale = 2.5, rippleRadius = 2, cornerRadius = 0.8, cameraPosition = [-9.31, 12, 24.72], cameraRotation = [-0.65, -0.2, -0.13], fov = 35, }: AnimatedGridPatternProps) { return (
) } ===== EXAMPLE: animated-grid-pattern-demo ===== Title: animated-grid-pattern-demo --- file: example/animated-grid-pattern-demo.tsx --- import { AnimatedGridPattern } from "@/registry/eldoraui/animated-grid-pattern" export function AnimatedGridPatternDemo() { return (
) } ===== COMPONENT: animated-list ===== Title: animated-list Description: An animated list component. --- file: eldoraui/animated-list.tsx --- "use client" import React, { useEffect, useMemo, useState } from "react" import { AnimatePresence, motion, useAnimationControls } from "motion/react" import { cn } from "@/lib/utils" type AnimationPhase = "idle" | "forming_column" | "scrolling_down" | "resetting" type AnimatedListProps = { children: React.ReactNode className?: string stackGap?: number columnGap?: number scaleFactor?: number scrollDownDuration?: number formationDuration?: number } type AnimatedListItemProps = { children: React.ReactNode className?: string index: number listLength: number stackGap?: number columnGap?: number scaleFactor?: number } function InternalAnimatedListItem({ children, className, index, listLength, animationPhase, onFormationComplete, stackGap = 10, columnGap = 100, scaleFactor = 0.1, formationDuration = 1, visibleItemsCount = 4, resetSpringStiffness = 120, resetSpringDamping = 20, }: AnimatedListItemProps & { animationPhase: AnimationPhase onFormationComplete?: () => void formationDuration: number visibleItemsCount: number resetSpringStiffness: number resetSpringDamping: number }) { const reverseIndex = listLength - 1 - index const isVisible = reverseIndex < visibleItemsCount const lastItemOffset = (listLength - 1) * columnGap const isLastItem = index === listLength - 1 const itemVariants = { initial: { scale: 1 + index * scaleFactor, y: reverseIndex * stackGap, opacity: isVisible ? 1 : 0, }, column: { scale: 1, y: index * columnGap - lastItemOffset, opacity: 1, }, } const target = animationPhase === "idle" || animationPhase === "resetting" ? "initial" : "column" const getTransition = () => { if (animationPhase === "resetting") { return { type: "spring" as const, stiffness: resetSpringStiffness, damping: resetSpringDamping, } } else { return { duration: formationDuration, ease: [0.4, 0, 0.2, 1] as const } } } const handleAnimationComplete = (definition: string) => { if ( isLastItem && definition === "column" && animationPhase === "forming_column" ) { onFormationComplete?.() } } return ( {children} ) } export function AnimatedList({ children, className, stackGap = 20, columnGap = 85, scaleFactor = 0.05, scrollDownDuration = 5, formationDuration = 1, }: AnimatedListProps) { const initialDelayValue = 500 const loopPauseDurationValue = 100 const listResetSpringStiffness = 100 const listResetSpringDamping = 25 const itemResetSpringStiffness = 120 const itemResetSpringDamping = 20 const visibleItemsCountValue = 4 const [animationPhase, setAnimationPhase] = useState("idle") const listControls = useAnimationControls() const childrenArray = useMemo( () => React.Children.toArray(children), [children] ) const listLength = childrenArray.length const totalHeight = listLength * columnGap useEffect(() => { let timer: NodeJS.Timeout if (animationPhase === "idle") { timer = setTimeout( () => { setAnimationPhase("forming_column") }, animationPhase === "idle" ? loopPauseDurationValue : initialDelayValue ) } return () => clearTimeout(timer) }, [animationPhase, loopPauseDurationValue, initialDelayValue]) const handleFormationComplete = () => { if (animationPhase === "forming_column") setAnimationPhase("scrolling_down") } const handleScrollDownComplete = () => { if (animationPhase === "scrolling_down") setAnimationPhase("resetting") } const handleScrollUpComplete = () => { if (animationPhase === "resetting") setAnimationPhase("idle") } useEffect(() => { if (animationPhase === "scrolling_down") { listControls.start({ y: totalHeight, transition: { duration: scrollDownDuration, ease: [0.4, 0, 0.2, 1] as const, }, }) } else if (animationPhase === "resetting") { listControls.start({ y: 0, transition: { type: "spring" as const, stiffness: listResetSpringStiffness, damping: listResetSpringDamping, }, }) } else { listControls.set({ y: 0 }) } }, [ animationPhase, listControls, totalHeight, scrollDownDuration, listResetSpringStiffness, listResetSpringDamping, ]) const handleListAnimationComplete = (definition: { y?: number }) => { if (definition.y === totalHeight && animationPhase === "scrolling_down") { handleScrollDownComplete() } else if (definition.y === 0 && animationPhase === "resetting") { handleScrollUpComplete() } } return ( {childrenArray.map((child, index) => ( {child} ))} ) } ===== EXAMPLE: animated-list-demo ===== Title: animated-list-demo --- file: example/animated-list-demo.tsx --- import { AnimatedList } from "@/registry/eldoraui/animated-list" export function AnimatedListDemo() { const notifications = [ { name: "Location", message: "Thomas has arrived home", time: "2h ago" }, { name: "Fitness", message: "Daily step goal reached!", time: "1h ago" }, { name: "Calendar", message: "Team meeting in 30 minutes", time: "45m ago", }, { name: "Tasks", message: "3 tasks due today", time: "1d ago" }, { name: "Health", message: "Heart rate elevated", time: "3h ago" }, { name: "Email", message: "New message from manager", time: "5m ago" }, { name: "Social", message: "Video got 1000 likes!", time: "2d ago" }, { name: "Family", message: "How are you doing?", time: "1w ago" }, { name: "Friends", message: "Coffee tomorrow?", time: "2d ago" }, { name: "Movies", message: "Did you see the new movie?", time: "4h ago" }, ] return (
{notifications.map((notification, index) => (
{notification.name.charAt(0)}
{notification.name} {notification.time}
{notification.message}
))}
) } ===== COMPONENT: animated-shiny-button ===== Title: animated-shiny-button Description: An animated shiny button component. --- file: eldoraui/animated-shiny-button.tsx --- "use client" import type React from "react" import { ChevronRight } from "lucide-react" interface AnimatedShinyButtonProps { children: React.ReactNode className?: string url?: string } export function AnimatedShinyButton({ children, className = "", url, }: AnimatedShinyButtonProps) { return ( <> {url ? ( {children} ) : ( )} ) } ===== EXAMPLE: animated-shiny-button-demo ===== Title: animated-shiny-button-demo --- file: example/animated-shiny-button-demo.tsx --- "use client" import { AnimatedShinyButton } from "@/registry/eldoraui/animated-shiny-button" export function AnimatedShinyButtonDemo() { return ( Get Started ) } ===== COMPONENT: blur-in-text ===== Title: blur-in-text Description: A blur in text component. --- file: eldoraui/blur-in-text.tsx --- "use client" import clsx from "clsx" import { motion } from "motion/react" interface BlurInTextProps { text?: string className?: string } export const BlurInText: React.FC = ({ text = "", className = "", }) => { const variants1 = { hidden: { filter: "blur(10px)", opacity: 0 }, visible: { filter: "blur(0px)", opacity: 1 }, } return ( {text} ) } ===== EXAMPLE: blur-in-text-demo ===== Title: blur-in-text-demo --- file: example/blur-in-text-demo.tsx --- import { BlurInText } from "@/registry/eldoraui/blur-in-text" export function BlurInTextDemo() { return ( ) } ===== COMPONENT: browser ===== Title: browser Description: A browser SVG component. --- file: eldoraui/browser.tsx --- "use client" import type React from "react" import { useEffect, useState } from "react" import Image from "next/image" import { Battery, BookmarkIcon, ChevronLeft, ChevronRight, Download, Globe, History, Home, Lock, Maximize2, Minimize2, MoreHorizontal, Plus, RotateCcw, Search, Settings, Shield, Square, Star, StarOff, Volume2, Wifi, X, } from "lucide-react" import { cn } from "@/lib/utils" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Card } from "@/components/ui/card" import { Input } from "@/components/ui/input" import { Separator } from "@/components/ui/separator" interface Tab { id: string title: string url: string favicon?: string isActive: boolean isLoading: boolean } interface Bookmark { id: string title: string url: string favicon?: string } interface HistoryItem { id: string title: string url: string timestamp: Date favicon?: string } interface BrowserProps { image?: string initialUrl?: string initialTabs?: Partial[] theme?: "light" | "dark" | "system" showWindowControls?: boolean showBookmarksBar?: boolean showStatusBar?: boolean className?: string enableTabManagement?: boolean enableBookmarks?: boolean enableHistory?: boolean enableDownloads?: boolean enableSettings?: boolean maxTabs?: number customBookmarks?: Bookmark[] customHistory?: HistoryItem[] onNavigate?: (url: string, tabId: string) => void onTabCreate?: (tab: Tab) => void onTabClose?: (tabId: string) => void onTabSwitch?: (tabId: string) => void onBookmarkToggle?: (url: string, isBookmarked: boolean) => void onDownload?: (url: string) => void renderContent?: (url: string, isLoading: boolean) => React.ReactNode customFavicons?: Record openLinksInNewTab?: boolean autoFocusAddressBar?: boolean simulateLoading?: boolean loadingDuration?: number } export function Browser({ image = "/placeholder.svg", initialUrl = "https://eldoraui.site", initialTabs, showWindowControls = false, showBookmarksBar = false, showStatusBar = true, className, enableTabManagement = false, enableBookmarks = true, enableHistory = true, enableDownloads = true, enableSettings = true, maxTabs = 10, customBookmarks, customHistory, onNavigate, onTabCreate, onTabClose, onTabSwitch, onBookmarkToggle, onDownload, renderContent, autoFocusAddressBar = false, simulateLoading = true, loadingDuration = 1000, }: BrowserProps = {}) { const [tabs, setTabs] = useState(() => { if (initialTabs && initialTabs.length > 0) { return initialTabs.map((tab, index) => ({ id: tab.id || Date.now().toString() + index, title: tab.title || "New Tab", url: tab.url || initialUrl, favicon: tab.favicon, isActive: index === 0, isLoading: false, })) } return [ { id: "1", title: "New Tab", url: initialUrl, isActive: true, isLoading: false, }, ] }) const [currentUrl, setCurrentUrl] = useState(initialUrl) const [inputUrl, setInputUrl] = useState(initialUrl) const [isSecure, setIsSecure] = useState(true) const [canGoBack, setCanGoBack] = useState(false) const [canGoForward, setCanGoForward] = useState(false) const [isBookmarked, setIsBookmarked] = useState(false) const [showBookmarks, setShowBookmarks] = useState(false) const [showHistory, setShowHistory] = useState(false) const [showSettings, setShowSettings] = useState(false) const [isFullscreen, setIsFullscreen] = useState(false) const [downloadProgress, setDownloadProgress] = useState(0) const [isDownloading, setIsDownloading] = useState(false) const [bookmarks] = useState( customBookmarks || [ { id: "1", title: "Google", url: "https://www.google.com", favicon: "🔍", }, { id: "2", title: "GitHub", url: "https://github.com", favicon: "🐙" }, { id: "3", title: "Stack Overflow", url: "https://stackoverflow.com", favicon: "📚", }, { id: "4", title: "MDN Web Docs", url: "https://developer.mozilla.org", favicon: "📖", }, ] ) const [history] = useState( customHistory || [ { id: "1", title: "Google", url: "https://www.google.com", timestamp: new Date(Date.now() - 3600000), favicon: "🔍", }, { id: "2", title: "GitHub", url: "https://github.com", timestamp: new Date(Date.now() - 7200000), favicon: "🐙", }, { id: "3", title: "Stack Overflow", url: "https://stackoverflow.com", timestamp: new Date(Date.now() - 10800000), favicon: "📚", }, ] ) const activeTab = tabs.find((tab) => tab.isActive) useEffect(() => { if (autoFocusAddressBar) { const addressBar = document.querySelector( 'input[placeholder*="Search or enter address"]' ) as HTMLInputElement if (addressBar) { addressBar.focus() } } }, [autoFocusAddressBar]) const createNewTab = () => { if (tabs.length >= maxTabs) return const newTab: Tab = { id: Date.now().toString(), title: "New Tab", url: "about:blank", isActive: true, isLoading: false, } setTabs((prev) => prev.map((tab) => ({ ...tab, isActive: false })).concat(newTab) ) setCurrentUrl("about:blank") setInputUrl("") onTabCreate?.(newTab) } const closeTab = (tabId: string) => { if (tabs.length === 1) return const tabIndex = tabs.findIndex((tab) => tab.id === tabId) const wasActive = tabs[tabIndex].isActive const newTabs = tabs.filter((tab) => tab.id !== tabId) if (wasActive && newTabs.length > 0) { const nextActiveIndex = Math.min(tabIndex, newTabs.length - 1) newTabs[nextActiveIndex].isActive = true setCurrentUrl(newTabs[nextActiveIndex].url) setInputUrl(newTabs[nextActiveIndex].url) } setTabs(newTabs) onTabClose?.(tabId) } const switchTab = (tabId: string) => { const newTabs = tabs.map((tab) => ({ ...tab, isActive: tab.id === tabId, })) const activeTab = newTabs.find((tab) => tab.isActive) if (activeTab) { setCurrentUrl(activeTab.url) setInputUrl(activeTab.url) } setTabs(newTabs) onTabSwitch?.(tabId) } const navigateToUrl = (url: string) => { if ( !url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("about:") ) { url = `https://www.google.com/search?q=${encodeURIComponent(url)}` } setCurrentUrl(url) setInputUrl(url) setIsSecure(url.startsWith("https://")) setTabs((prev) => prev.map((tab) => tab.isActive ? { ...tab, url, title: new URL(url).hostname || "New Tab", isLoading: simulateLoading, } : tab ) ) const activeTabId = tabs.find((tab) => tab.isActive)?.id || "" onNavigate?.(url, activeTabId) if (simulateLoading) { setTimeout(() => { setTabs((prev) => prev.map((tab) => (tab.isActive ? { ...tab, isLoading: false } : tab)) ) }, loadingDuration) } } const handleUrlSubmit = (e: React.FormEvent) => { e.preventDefault() navigateToUrl(inputUrl) } const goBack = () => { setCanGoForward(true) } const goForward = () => { setCanGoBack(true) } const refresh = () => { setTabs((prev) => prev.map((tab) => (tab.isActive ? { ...tab, isLoading: true } : tab)) ) setTimeout(() => { setTabs((prev) => prev.map((tab) => (tab.isActive ? { ...tab, isLoading: false } : tab)) ) }, 1000) } const toggleBookmark = () => { const newBookmarkedState = !isBookmarked setIsBookmarked(newBookmarkedState) onBookmarkToggle?.(currentUrl, newBookmarkedState) } const simulateDownload = () => { onDownload?.(currentUrl) if (!enableDownloads) return setIsDownloading(true) setDownloadProgress(0) const interval = setInterval(() => { setDownloadProgress((prev) => { if (prev >= 100) { clearInterval(interval) setIsDownloading(false) return 0 } return prev + 10 }) }, 200) } return (
{showWindowControls && (
{new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", })}
)} {enableTabManagement && (
{tabs.map((tab) => (
switchTab(tab.id)} >
{tab.isLoading ? (
) : ( )} {tab.title}
{tabs.length > 1 && ( )}
))}
)}
{isSecure ? ( ) : ( )}
setInputUrl(e.target.value)} placeholder="Search or enter address" className="pr-4 pl-10" />
{enableBookmarks && ( )} {enableDownloads && ( )} {enableSettings && ( )}
{showBookmarksBar && enableBookmarks && (
{enableHistory && ( )} {bookmarks.slice(0, 4).map((bookmark) => ( ))}
)} {isDownloading && enableDownloads && (
Downloading file...
{downloadProgress}%
)}
{showBookmarks && enableBookmarks && (

Bookmarks

{bookmarks.map((bookmark) => (
navigateToUrl(bookmark.url)} > {bookmark.favicon}
{bookmark.title}
{bookmark.url}
))}
)} {showHistory && enableHistory && (

History

{history.map((item) => (
navigateToUrl(item.url)} > {item.favicon}
{item.title}
{item.url}
{item.timestamp.toLocaleTimeString()}
))}
)} {showSettings && enableSettings && (

Settings

Privacy & Security

Block pop-ups On
Safe browsing Enhanced

Appearance

Theme System
Zoom 100%
)}
{renderContent ? ( renderContent(currentUrl, activeTab?.isLoading || false) ) : currentUrl === "about:blank" || currentUrl === "" ? (

New Tab

Start by searching or entering a web address

{bookmarks.slice(0, 4).map((bookmark) => ( navigateToUrl(bookmark.url)} >
{bookmark.favicon}
{bookmark.title}
))}
) : (
{image}
)}
{showStatusBar && (
Ready {isSecure && ( Secure )}
Zoom: 100% {tabs.length} tab{tabs.length !== 1 ? "s" : ""}
)}
) } ===== EXAMPLE: browser-demo ===== Title: browser-demo --- file: example/browser-demo.tsx --- import { Browser } from "@/registry/eldoraui/browser" export function BrowserDemo() { return (
) } ===== EXAMPLE: browser-demo-2 ===== Title: browser-demo-2 --- file: example/browser-demo-2.tsx --- import { Browser } from "@/registry/eldoraui/browser" export function BrowserDemo2() { return (
) } ===== COMPONENT: card-flip-hover ===== Title: card-flip-hover Description: A card flip hover component. --- file: eldoraui/card-flip-hover.tsx --- "use client" import React, { useState } from "react" interface CardFlipHoverProps { imageUrl: string } export const CardFlipHover = ({ imageUrl }: CardFlipHoverProps) => { const [isFlipped, setIsFlipped] = useState(false) const handleHover = () => { if (!isFlipped) { setIsFlipped(true) setTimeout(() => setIsFlipped(false), 700) } } return (
{/* Front Side */}
Front
{/* Back Side */}
Back
) } ===== EXAMPLE: card-flip-hover-demo ===== Title: card-flip-hover-demo --- file: example/card-flip-hover-demo.tsx --- import { CardFlipHover } from "@/registry/eldoraui/card-flip-hover" export default function DemoOne() { return (
) } ===== COMPONENT: clerk-otp ===== Title: clerk-otp Description: A clerk OTP component. --- file: eldoraui/clerk-otp.tsx --- "use client" import { useEffect, useState } from "react" import { motion } from "motion/react" import { cn } from "@/lib/utils" const generateRandomDigits = () => { return Array.from({ length: 6 }, () => Math.floor(Math.random() * 10).toString() ) } type AnimatedOTPProps = { delay?: number cardTitle?: string cardDescription?: string whileHover?: boolean } const AnimatedOTP = ({ delay = 3500, cardTitle = "Multifactor Authentication", cardDescription = "Each user's self-serve multifactor settings are enforced automatically during sign-in.", whileHover = false, }: AnimatedOTPProps) => { const [animationKey, setAnimationKey] = useState(0) const delayTime = Math.max(delay, 3500) useEffect(() => { const interval = setInterval(() => { setAnimationKey((prev) => prev + 1) }, delayTime) return () => clearInterval(interval) }, [delayTime]) return ( ) } export default AnimatedOTP const OTPinput = ({ cardTitle, cardDescription, whileHover, }: AnimatedOTPProps) => { const [activeIndex, setActiveIndex] = useState(0) const [fadeOut, setFadeOut] = useState(false) const [digits] = useState(() => generateRandomDigits()) const [isCardHovered, setIsCardHovered] = useState(false) useEffect(() => { if (activeIndex > digits.length - 1) return const shouldAnimate = !whileHover || (whileHover && isCardHovered) if (!shouldAnimate) return const interval = setInterval(() => { setActiveIndex((prev) => prev + 1) }, 400) if (activeIndex === digits.length - 1) { setTimeout(() => { setFadeOut(true) }, 450) } return () => clearInterval(interval) }, [activeIndex, digits.length, whileHover, isCardHovered]) return ( setIsCardHovered(true)} onHoverEnd={() => setIsCardHovered(false)} className={cn( "relative", "flex items-center justify-center", "h-[14rem] w-full max-w-[350px]", "rounded-md border bg-neutral-50 dark:bg-neutral-900", "shadow-[0_3px_10px_rgb(0,0,0,0.2)]" )} >
{digits.map((digit, idx) => (
{(!whileHover || (whileHover && isCardHovered)) && ( )} {activeIndex === idx && (!whileHover || (whileHover && isCardHovered)) && ( )} {digit}
))}

{cardTitle}

{cardDescription}

) } ===== EXAMPLE: clerk-otp-demo ===== Title: clerk-otp-demo --- file: example/clerk-otp-demo.tsx --- import ClerkOTP from "@/registry/eldoraui/clerk-otp" export function ClerkOTPDemo() { return ( ) } ===== EXAMPLE: clerk-otp-demo-2 ===== Title: clerk-otp-demo-2 --- file: example/clerk-otp-demo-2.tsx --- import ClerkOTP from "@/registry/eldoraui/clerk-otp" export function ClerkOTPDemo2() { return ( ) } ===== COMPONENT: cobe-globe ===== Title: cobe-globe Description: A cobe globe component. --- file: eldoraui/cobe-globe.tsx --- "use client" import { useCallback, useEffect, useRef, useState } from "react" import createGlobe from "cobe" import { useSpring } from "react-spring" import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button" type CobeVariant = | "default" | "draggable" | "auto-draggable" | "auto-rotation" | "rotate-to-location" | "scaled" interface Location { name: string lat?: number long?: number emoji?: string } interface GeocodeResult { lat: number lng: number display_name: string } interface CobeProps { variant?: CobeVariant className?: string style?: React.CSSProperties locations?: Location[] // Globe configuration settings phi?: number theta?: number mapSamples?: number mapBrightness?: number mapBaseBrightness?: number diffuse?: number dark?: number baseColor?: string markerColor?: string markerSize?: number glowColor?: string scale?: number offsetX?: number offsetY?: number opacity?: number } type CobeState = Record export function Cobe({ variant = "default", className, style, locations = [ { name: "San Francisco", emoji: "📍" }, { name: "Berlin", emoji: "📍" }, { name: "Tokyo", emoji: "📍" }, { name: "Buenos Aires", emoji: "📍" }, ], // Default values based on the original JSX version phi = 0, theta = 0.2, mapSamples = 16000, mapBrightness = 1.8, mapBaseBrightness = 0.05, diffuse = 3, dark = 1.0, baseColor = "#ffffff", markerColor = "#fb6415", markerSize = 0.05, glowColor = "#ffffff", scale = 1.0, offsetX = 0.0, offsetY = 0.0, opacity = 0.7, }: CobeProps) { const canvasRef = useRef(null) const pointerInteracting = useRef(null) const pointerInteractionMovement = useRef(0) const focusRef = useRef<[number, number]>([0, 0]) const [customLocations, setCustomLocations] = useState([]) const [isInitializing, setIsInitializing] = useState(true) const [{ r }, api] = useSpring<{ r: number }>(() => ({ r: 0, config: { mass: 1, tension: 280, friction: 40, precision: 0.001, }, })) const locationToAngles = (lat: number, long: number): [number, number] => { return [ Math.PI - ((long * Math.PI) / 180 - Math.PI / 2), (lat * Math.PI) / 180, ] as [number, number] } const hexToRgb = (hex: string): [number, number, number] => { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) return result ? [ parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255, ] : [0, 0, 0] } const geocodeLocation = async ( query: string ): Promise => { try { const response = await fetch( `https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(query)}&limit=1` ) const data = await response.json() if (data && data.length > 0) { return { lat: parseFloat(data[0].lat), lng: parseFloat(data[0].lon), display_name: data[0].display_name, } } return null } catch (error) { console.error("Geocoding error:", error) return null } } const geocodeLocationList = useCallback(async (locationList: Location[]) => { const geocodedLocations: Location[] = [] for (const location of locationList) { if (location.lat && location.long) { // Already has coordinates geocodedLocations.push(location) } else { // Need to geocode const result = await geocodeLocation(location.name) if (result) { geocodedLocations.push({ ...location, lat: result.lat, long: result.lng, }) } } } return geocodedLocations }, []) // Initialize locations on component mount useEffect(() => { const initializeLocations = async () => { if (variant === "rotate-to-location" && locations.length > 0) { setIsInitializing(true) const geocoded = await geocodeLocationList(locations) setCustomLocations(geocoded) setIsInitializing(false) } } initializeLocations() }, [variant, locations, geocodeLocationList]) useEffect(() => { let phi = 0 let width = 0 let currentPhi = 0 let currentTheta = 0 const doublePi = Math.PI * 2 const onResize = () => { if (canvasRef.current) { width = canvasRef.current.offsetWidth } } window.addEventListener("resize", onResize) onResize() if (!canvasRef.current) return const globe = createGlobe(canvasRef.current, { devicePixelRatio: 2, width: width * 2, height: variant === "scaled" ? width * 2 * 0.4 : width * 2, phi: phi, theta: theta, dark: dark, diffuse: diffuse, mapSamples: mapSamples, mapBrightness: mapBrightness, mapBaseBrightness: mapBaseBrightness, baseColor: hexToRgb(baseColor), markerColor: hexToRgb(markerColor), glowColor: hexToRgb(glowColor), markers: variant === "default" || variant === "draggable" || variant === "auto-draggable" || variant === "auto-rotation" || variant === "scaled" ? [ // San Francisco, default color { location: [37.7595, -122.4367], size: markerSize }, // New York, red color { location: [40.7128, -74.006], size: markerSize, color: [1, 0, 0], }, // Tokyo, blue color { location: [35.6895, 139.6917], size: markerSize, color: [0, 0.5, 1], }, // Sydney, green color { location: [-33.8688, 151.2093], size: markerSize, color: [0, 1, 0], }, // Rio de Janeiro, purple color { location: [-22.9068, -43.1729], size: markerSize, color: [0.8, 0, 0.8], }, // Paris, yellow color { location: [48.8566, 2.3522], size: markerSize, color: [1, 1, 0], }, // Porto, orange color { location: [41.1579, -8.6291], size: markerSize, color: [1, 0.5, 0], }, // Athens, pink color { location: [37.9838, 23.7275], size: markerSize, color: [1, 0.5, 1], }, // Rome, brown color { location: [41.9028, 12.4964], size: markerSize, color: [0.5, 0.3, 0], }, // Kathmandu, blue color { location: [27.7172, 85.324], size: markerSize, color: [0, 0.5, 1], }, // Tarbes, green color { location: [43.4643, -0.5167], size: markerSize, color: [0, 1, 0], }, // Bamako, yellow color { location: [12.6683, -8.0076], size: markerSize, color: [1, 1, 0], }, // Djibouti, purple color { location: [11.55, 43.1667], size: markerSize, color: [0.8, 0, 0.8], }, ] : variant === "rotate-to-location" ? customLocations .filter((loc) => loc.lat && loc.long) .map((loc) => ({ location: [loc.lat!, loc.long!], size: markerSize, })) : [], scale: variant === "scaled" ? 2.5 : undefined, offset: variant === "scaled" ? [0, width * 2 * 0.4 * 0.6] : undefined, opacity: opacity, onRender: (state: CobeState) => { switch (variant) { case "default": state.phi = phi + r.get() phi += 0.005 break case "draggable": state.phi = r.get() break case "auto-draggable": if (!pointerInteracting.current) { phi += 0.005 } state.phi = phi + r.get() break case "auto-rotation": state.phi = phi phi += 0.005 break case "rotate-to-location": state.phi = currentPhi state.theta = currentTheta const [focusPhi, focusTheta] = focusRef.current const distPositive = (focusPhi - currentPhi + doublePi) % doublePi const distNegative = (currentPhi - focusPhi + doublePi) % doublePi if (distPositive < distNegative) { currentPhi += distPositive * 0.08 } else { currentPhi -= distNegative * 0.08 } currentTheta = currentTheta * 0.92 + focusTheta * 0.08 break case "scaled": // No rotation for scaled variant break } state.width = width * 2 state.height = variant === "scaled" ? width * 2 * 0.4 : width * 2 }, }) if (canvasRef.current) { setTimeout(() => { if (canvasRef.current) { canvasRef.current.style.opacity = opacity.toString() } }) } return () => { globe.destroy() window.removeEventListener("resize", onResize) } }, [ variant, r, customLocations, phi, theta, mapSamples, mapBrightness, mapBaseBrightness, diffuse, dark, baseColor, markerColor, markerSize, glowColor, scale, offsetX, offsetY, opacity, ]) const handlePointerDown = (e: React.PointerEvent) => { if ( variant === "draggable" || variant === "auto-draggable" || variant === "default" ) { pointerInteracting.current = e.clientX - pointerInteractionMovement.current if (canvasRef.current) canvasRef.current.style.cursor = "grabbing" } } const handlePointerUp = () => { if ( variant === "draggable" || variant === "auto-draggable" || variant === "default" ) { pointerInteracting.current = null if (canvasRef.current) canvasRef.current.style.cursor = "grab" } } const handlePointerOut = () => { if ( variant === "draggable" || variant === "auto-draggable" || variant === "default" ) { pointerInteracting.current = null if (canvasRef.current) canvasRef.current.style.cursor = "grab" } } const handleMouseMove = (e: React.MouseEvent) => { if ( (variant === "draggable" || variant === "auto-draggable" || variant === "default") && pointerInteracting.current !== null ) { const delta = e.clientX - pointerInteracting.current pointerInteractionMovement.current = delta api.start({ r: delta / 200, }) } } const handleTouchMove = (e: React.TouchEvent) => { if ( (variant === "draggable" || variant === "auto-draggable" || variant === "default") && pointerInteracting.current !== null && e.touches[0] ) { const delta = e.touches[0].clientX - pointerInteracting.current pointerInteractionMovement.current = delta api.start({ r: delta / 100, }) } } const handleLocationClick = (lat: number, long: number) => { if (variant === "rotate-to-location") { focusRef.current = locationToAngles(lat, long) } } const containerStyle = { width: "100%", maxWidth: variant === "scaled" ? 800 : 600, aspectRatio: variant === "scaled" ? 2.5 : 1, margin: "auto", position: "relative" as const, ...style, } const canvasStyle = { width: "100%", height: "100%", contain: "layout paint size" as const, opacity: 0, transition: "opacity 1s ease", cursor: variant === "draggable" || variant === "auto-draggable" || variant === "default" ? "grab" : undefined, borderRadius: variant === "default" || variant === "draggable" || variant === "auto-draggable" || variant === "auto-rotation" ? "50%" : variant === "scaled" ? "8px" : undefined, } return (
{variant === "rotate-to-location" && ( <>
{isInitializing ? "Loading locations..." : ""} {customLocations .filter((loc) => loc.lat && loc.long) .map((location, index) => ( ))}
)}
) } ===== EXAMPLE: cobe-globe-demo ===== Title: cobe-globe-demo --- file: example/cobe-globe-demo.tsx --- "use client" import { Cobe } from "@/registry/eldoraui/cobe-globe" export function CobeGlobeDemo() { return (
) } ===== EXAMPLE: cobe-globe-demo-2 ===== Title: cobe-globe-demo-2 --- file: example/cobe-globe-demo-2.tsx --- "use client" import { Cobe } from "@/registry/eldoraui/cobe-globe" export function CobeGlobeDemo2() { return (
) } ===== EXAMPLE: cobe-globe-demo-3 ===== Title: cobe-globe-demo-3 --- file: example/cobe-globe-demo-3.tsx --- "use client" import { Cobe } from "@/registry/eldoraui/cobe-globe" export function CobeGlobeDemo3() { return (
) } ===== EXAMPLE: cobe-globe-demo-4 ===== Title: cobe-globe-demo-4 --- file: example/cobe-globe-demo-4.tsx --- "use client" import { Cobe } from "@/registry/eldoraui/cobe-globe" export function CobeGlobeDemo4() { const customLocations = [ { name: "Hyderabad,India", emoji: "🇮🇳" }, { name: "New York,USA", emoji: "🇺🇸" }, { name: "California", emoji: "🇺🇸" }, { name: "Paris", emoji: "🇫🇷" }, { name: "London", emoji: "🇬🇧" }, ] return (
) } ===== EXAMPLE: cobe-globe-demo-5 ===== Title: cobe-globe-demo-5 --- file: example/cobe-globe-demo-5.tsx --- "use client" import { Cobe } from "@/registry/eldoraui/cobe-globe" export function CobeGlobeDemo5() { return (
) } ===== COMPONENT: dock-text ===== Title: dock-text Description: A dock text component with interactive hover effects. --- file: eldoraui/dock-text.tsx --- "use client" import React, { useRef, useState } from "react" import { motion } from "motion/react" import { cn } from "@/lib/utils" interface DockTextProps { text: string down?: boolean className?: string } export default function DockText({ text, down = false, className, }: DockTextProps) { const [hoveredIndex, setHoveredIndex] = useState(null) const containerRef = useRef(null) const handleMouseMove = (e: React.MouseEvent) => { const container = containerRef.current if (!container) return const letters = container.children const containerRect = container.getBoundingClientRect() const mouseX = e.clientX - containerRect.left Array.from(letters).forEach((letter, index) => { const letterRect = letter.getBoundingClientRect() const letterCenterX = letterRect.left + letterRect.width / 2 - containerRect.left const distance = Math.abs(mouseX - letterCenterX) if (distance <= 10) { setHoveredIndex(index) } }) } const handleMouseLeave = () => { setHoveredIndex(null) } return ( {text.split("").map((letter, index) => ( {letter} ))} ) } ===== EXAMPLE: dock-text-demo ===== Title: dock-text-demo --- file: example/dock-text-demo.tsx --- "use client" import DockText from "@/registry/eldoraui/dock-text" export default function DockTextDemo() { return } ===== EXAMPLE: dock-text-demo-2 ===== Title: dock-text-demo-2 --- file: example/dock-text-demo-2.tsx --- "use client" import DockText from "@/registry/eldoraui/dock-text" export default function DockTextDemo2() { return } ===== COMPONENT: fade-text ===== Title: fade-text Description: A combined fade text component with support for in, up, and down directions. --- file: eldoraui/fade-text.tsx --- "use client" import React from "react" import clsx from "clsx" import { motion } from "motion/react" type FadeDirection = "in" | "up" | "down" interface FadeTextProps { text?: string className?: string direction?: FadeDirection staggerDelay?: number wordDelay?: number } export const FadeText: React.FC = ({ text = "", className = "", direction = "in", staggerDelay = 0.15, wordDelay = 0.1, }) => { // For "in" direction, we animate word by word if (direction === "in") { const words = text.split(" ") return ( {words.map((word, i) => ( {word}{" "} ))} ) } // For "up" and "down" directions, we animate the entire text const animationVariants = direction === "up" ? { hidden: { opacity: 0, y: 20 }, show: { opacity: 1, y: 0, transition: { type: "spring" as const, stiffness: 100, damping: 12, duration: 0.8, }, }, } : { hidden: { opacity: 0, y: -20 }, show: { opacity: 1, y: 0, transition: { type: "spring" as const, stiffness: 100, damping: 12, duration: 0.8, }, }, } return ( {text} ) } ===== EXAMPLE: fade-text-demo ===== Title: fade-text-demo --- file: example/fade-text-demo.tsx --- import { FadeText } from "@/registry/eldoraui/fade-text" export default function FadeTextDemo() { return ( ) } ===== EXAMPLE: fade-text-demo-2 ===== Title: fade-text-demo-2 --- file: example/fade-text-demo-2.tsx --- import { FadeText } from "@/registry/eldoraui/fade-text" export default function FadeTextDemo2() { return ( ) } ===== EXAMPLE: fade-text-demo-3 ===== Title: fade-text-demo-3 --- file: example/fade-text-demo-3.tsx --- import { FadeText } from "@/registry/eldoraui/fade-text" export default function FadeTextDemo3() { return ( ) } ===== COMPONENT: font-weight-text ===== Title: font-weight-text Description: A font weight text component. --- file: eldoraui/font-weight-text.tsx --- "use client" import { useEffect, useRef } from "react" import { cn } from "@/lib/utils" interface FontWeightTextProps { text: string className?: string fontSize?: number minWeight?: number maxWeight?: number animationDuration?: number delayMultiplier?: number } export function FontWeightText({ text, className = "", fontSize = 150, minWeight = 0, maxWeight = 840, animationDuration = 1.5, delayMultiplier = 0.25, }: FontWeightTextProps) { const containerRef = useRef(null) useEffect(() => { if (!containerRef.current) return const spans = containerRef.current.querySelectorAll("span") const numLetters = spans.length spans.forEach((span, i) => { const mappedIndex = i - numLetters / 2 span.style.animationDelay = mappedIndex * delayMultiplier + "s" }) }, [text, delayMultiplier]) const characters = text.split("").map((char, index) => ( )) return (

{characters}

) } ===== EXAMPLE: font-weight-text-demo ===== Title: font-weight-text-demo --- file: example/font-weight-text-demo.tsx --- import { FontWeightText } from "@/registry/eldoraui/font-weight-text" export function FontWeightTextDemo() { return (
) } ===== COMPONENT: github-inline-comments ===== Title: github-inline-comments Description: A github inline comments component. --- file: eldoraui/github-inline-comments.tsx --- "use client" import { useEffect, useRef, useState } from "react" import { CheckCircle2, MessageSquarePlus, X } from "lucide-react" import { cn } from "@/lib/utils" import { Avatar, AvatarFallback } from "@/components/ui/avatar" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Separator } from "@/components/ui/separator" import { Textarea } from "@/components/ui/textarea" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip" type Line = | { kind: "hunk"; content: string } | { kind: "context"; old: number | null; new: number | null; content: string } | { kind: "add"; old: number | null; new: number | null; content: string } | { kind: "del"; old: number | null; new: number | null; content: string } export default function GithubInlineComments({ diff, fileName, }: { diff: readonly Line[] fileName: string }) { return } function DiffList({ diff, fileName, }: { diff: readonly Line[] fileName: string }) { const rows = diff ?? [] // Tracks which line index currently has an open thread const [openThreadAt, setOpenThreadAt] = useState(null) // Tracks thread status per line const [resolvedMap, setResolvedMap] = useState>({}) function toggleResolve(idx: number) { setResolvedMap((m) => ({ ...m, [idx]: !m[idx] })) } return (
{fileName} modified
    {rows.map((line, idx) => { const isChange = line.kind === "add" || line.kind === "del" const isOpen = openThreadAt === idx const isResolved = !!resolvedMap[idx] return (
  1. {line.kind !== "hunk" && ( Add comment )}
    {line.kind === "add" ? "" : line.kind === "hunk" ? "" : (line.old ?? "")} {line.kind === "del" ? "" : line.kind === "hunk" ? "" : (line.new ?? "")}
                        
                          {line.kind === "add"
                            ? "+"
                            : line.kind === "del"
                              ? "-"
                              : " "}
                        
                        {line.content}
                      
    {openThreadAt === idx && line.kind !== "hunk" && (
    toggleResolve(idx)} onClose={() => setOpenThreadAt(null)} />
    )}
  2. ) })}
) } type Comment = { id: string author: string initials: string body: string createdAt: string } function InlineThread({ resolved, onToggleResolve, onClose, }: { resolved: boolean onToggleResolve: () => void onClose: () => void }) { const [comments, setComments] = useState([ { id: "c1", author: "Reviewer", initials: "RV", body: "Consider handling the undefined case explicitly.", createdAt: "just now", }, ]) const [draft, setDraft] = useState("") const textRef = useRef(null) function addComment() { const text = draft.trim() if (!text) return setComments((c) => [ ...c, { id: crypto.randomUUID(), author: "You", initials: "YO", body: text, createdAt: "now", }, ]) setDraft("") // focus back for fast sequences requestAnimationFrame(() => textRef.current?.focus()) } useEffect(() => { function onKeyDown(e: KeyboardEvent) { if (e.key === "Escape") { onClose() } } window.addEventListener("keydown", onKeyDown) return () => window.removeEventListener("keydown", onKeyDown) }, [onClose]) return (
{/* Header with status chip */}
{resolved ? : null} {resolved ? "Resolved" : "Open"}
{/* Comments list */}
    {comments.map((c) => (
  • {c.initials}

    {c.author}

    {c.createdAt}

    {c.body}

  • ))}
{/* Editor */}