OonkooUI
Home/Docs/Component Guide

Component Submission Guide

Everything you need to know to submit components to OonkooUI

Required File Structure

Every submission requires exactly 2 files:

Submission Structure
ui/your-component.tsxMain Component
preview/page.tsxPreview Page

How it works: Our admin team will copy your files to the codebase, install any dependencies you list, and manually test the component. Once approved, your component gets published with you as the author.

1. Component File (ui/)

This is your core component that users will install. Place it in the ui/ folder.

ui/glow-button.tsx
"use client"

import { cn } from "@/lib/utils"

interface GlowButtonProps {
  children: React.ReactNode
  className?: string
  variant?: "purple" | "blue" | "green"
}

export function GlowButton({
  children,
  className,
  variant = "purple"
}: GlowButtonProps) {
  const colors = {
    purple: "bg-purple-600 hover:shadow-purple-500/50",
    blue: "bg-blue-600 hover:shadow-blue-500/50",
    green: "bg-green-600 hover:shadow-green-500/50",
  }

  return (
    <button
      className={cn(
        "px-6 py-3 rounded-lg text-white font-medium",
        "hover:shadow-[0_0_30px_rgba(0,0,0,0.3)]",
        "transition-all duration-300",
        colors[variant],
        className
      )}
    >
      {children}
    </button>
  )
}

2. Preview File (preview/)

This page demonstrates your component in action. Shows admins and users how to use it.

preview/page.tsx
import { GlowButton } from "@/components/ui/glow-button"

export default function PreviewPage() {
  return (
    <div className="min-h-screen flex flex-col items-center justify-center gap-8 bg-background p-8">
      <h1 className="text-3xl font-bold">Glow Button</h1>

      {/* Default variant */}
      <GlowButton>Click Me</GlowButton>

      {/* All variants */}
      <div className="flex gap-4">
        <GlowButton variant="purple">Purple</GlowButton>
        <GlowButton variant="blue">Blue</GlowButton>
        <GlowButton variant="green">Green</GlowButton>
      </div>
    </div>
  )
}

NPM Dependencies

If your component uses external packages like framer-motion,three, gsap, or @radix-ui, list them in the dependencies field.

Example Dependencies
framer-motionthree@react-three/fibergsap
npm i framer-motion three @react-three/fiber gsap

Admin will run this command before testing your component.

ui/liquid-sphere.tsx (with Three.js)
"use client"

import { useRef } from "react"
import { Canvas, useFrame } from "@react-three/fiber"
import * as THREE from "three"

function Sphere() {
  const meshRef = useRef<THREE.Mesh>(null)

  useFrame((state) => {
    if (meshRef.current) {
      meshRef.current.rotation.y += 0.01
    }
  })

  return (
    <mesh ref={meshRef}>
      <sphereGeometry args={[1, 32, 32]} />
      <meshStandardMaterial
        color="#a855f7"
        metalness={0.8}
        roughness={0.2}
      />
    </mesh>
  )
}

export function LiquidSphere() {
  return (
    <div className="w-full h-[400px]">
      <Canvas camera={{ position: [0, 0, 3] }}>
        <ambientLight intensity={0.5} />
        <pointLight position={[10, 10, 10]} />
        <Sphere />
      </Canvas>
    </div>
  )
}

Styling Guidelines

Use Tailwind CSS

All Tailwind utility classes are available

Use cn() for conditional classes

Import from @/lib/utils: cn("base-class", condition && "active")

Use theme CSS variables

Colors like bg-background, text-foreground, text-muted-foreground

Use Lucide React icons

Import from lucide-react: import { Sparkles } from 'lucide-react'

What Works

These are available and will work in your components:

React Hooks

useState, useEffect, useRef, etc.

Tailwind CSS

All utility classes

Lucide Icons

Icon library

cn() utility

Class merging

TypeScript

Full type support

framer-motion

List as dependency

Three.js

List as dependency

GSAP

List as dependency

These won't work:

API calls

No fetch, axios, or server calls

Environment variables

No process.env access

Database access

No Prisma or DB queries

Server components

No async components

Complete Examples

1. Interactive Button with State

ui/pulse-button.tsx
"use client"

import { useState } from "react"
import { cn } from "@/lib/utils"
import { Sparkles } from "lucide-react"

interface PulseButtonProps {
  children: React.ReactNode
  className?: string
}

export function PulseButton({ children, className }: PulseButtonProps) {
  const [isPulsing, setIsPulsing] = useState(false)

  return (
    <button
      className={cn(
        "relative px-6 py-3 rounded-lg",
        "bg-gradient-to-r from-purple-600 to-pink-600",
        "text-white font-medium",
        "hover:scale-105 transition-transform",
        isPulsing && "animate-pulse",
        className
      )}
      onClick={() => {
        setIsPulsing(true)
        setTimeout(() => setIsPulsing(false), 1000)
      }}
    >
      <Sparkles className="inline-block w-4 h-4 mr-2" />
      {children}
    </button>
  )
}

2. Animated Card (requires framer-motion)

ui/float-card.tsx
"use client"

import { motion } from "framer-motion"
import { cn } from "@/lib/utils"

interface FloatCardProps {
  title: string
  children: React.ReactNode
  className?: string
}

export function FloatCard({ title, children, className }: FloatCardProps) {
  return (
    <motion.div
      initial={{ y: 20, opacity: 0 }}
      animate={{ y: 0, opacity: 1 }}
      whileHover={{ y: -5, scale: 1.02 }}
      transition={{ type: "spring", stiffness: 300 }}
      className={cn(
        "p-6 rounded-xl border bg-card",
        "shadow-lg hover:shadow-xl transition-shadow",
        className
      )}
    >
      <h3 className="text-xl font-bold mb-2">{title}</h3>
      {children}
    </motion.div>
  )
}

3. 3D Element (requires three, @react-three/fiber)

ui/spinning-cube.tsx
"use client"

import { useRef } from "react"
import { Canvas, useFrame } from "@react-three/fiber"
import * as THREE from "three"

function Cube() {
  const meshRef = useRef<THREE.Mesh>(null)

  useFrame(() => {
    if (meshRef.current) {
      meshRef.current.rotation.x += 0.01
      meshRef.current.rotation.y += 0.01
    }
  })

  return (
    <mesh ref={meshRef}>
      <boxGeometry args={[1.5, 1.5, 1.5]} />
      <meshNormalMaterial />
    </mesh>
  )
}

export function SpinningCube() {
  return (
    <div className="w-full h-[300px] rounded-lg overflow-hidden">
      <Canvas camera={{ position: [0, 0, 4] }}>
        <ambientLight intensity={0.5} />
        <Cube />
      </Canvas>
    </div>
  )
}

Ready to Submit?

Create your two files, list any dependencies, and submit. Our team manually tests every component for quality.