Docs
Go back
Component by
OV
OblivionCreate ImageData AnalysisResearch
TSX
ai-narrative-input
"use client"
import React, { useState } from "react"
import { motion, AnimatePresence } from "framer-motion"
import { Paperclip, Globe, Plus, Send, Zap } from "lucide-react"
import { cn } from "@/lib/utils"
export const componentMeta = {
name: "AI Narrative Input",
description: "A premium, physics-based AI prompt input with magnetic actions and fluid glassmorphism.",
props: {
placeholder: { type: "string", description: "The text area placeholder", default: '"Ask me anything..."' },
onSend: { type: "function", description: "Callback when the prompt is submitted" }
}
}
export default function AiNarrativeInput({
placeholder = "Ask me anything...",
onSend
}: {
placeholder?: string
onSend?: (prompt: string) => void
}) {
const [prompt, setPrompt] = useState("")
const [isFocused, setIsFocused] = useState(false)
const handleSend = () => {
if (prompt.trim()) {
onSend?.(prompt)
setPrompt("")
}
}
return (
<div className="flex flex-col w-full max-w-sm gap-4 font-sans antialiased">
{/* Container with Glassmorphism Border */}
<div
className={cn(
"relative group p-[1.5px] rounded-2xl transition-all duration-500",
isFocused
? "bg-gradient-to-br from-zinc-400 via-zinc-600 to-zinc-400 shadow-[0_0_20px_rgba(255,255,255,0.1)]"
: "bg-gradient-to-br from-zinc-600 to-zinc-900 shadow-xl"
)}
>
{/* Animated Corner Light */}
<div className="absolute top-[-10px] left-[-10px] w-12 h-12 bg-white/20 blur-xl pointer-events-none rounded-full" />
<div className="relative flex flex-col bg-black/90 backdrop-blur-xl rounded-[15px] overflow-hidden border border-white/5">
<textarea
value={prompt}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setPrompt(e.target.value)}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
placeholder={placeholder}
className="w-full h-24 bg-transparent border-none text-white text-sm p-4 resize-none outline-none placeholder:text-zinc-500 placeholder:transition-colors focus:placeholder:text-zinc-700 transition-all duration-300"
/>
<div className="flex items-center justify-between p-3 pt-0">
{/* Tool Buttons */}
<div className="flex gap-2">
{[Paperclip, Plus, Globe].map((Icon, i) => (
<motion.button
key={i}
whileHover={{ y: -3, color: "#fff" }}
whileTap={{ scale: 0.9 }}
className="p-2 text-zinc-600 transition-colors"
>
<Icon size={18} />
</motion.button>
))}
</div>
{/* Submit Button */}
<motion.button
onClick={handleSend}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className={cn(
"group relative flex items-center justify-center w-10 h-10 rounded-xl bg-zinc-800 border border-white/10 transition-all duration-300",
prompt ? "bg-white text-black shadow-[0_0_15px_rgba(255,255,255,0.3)]" : "text-zinc-600"
)}
>
<AnimatePresence mode="wait">
<motion.div
key={prompt ? "send" : "zap"}
initial={{ opacity: 0, scale: 0.5, rotate: -45 }}
animate={{ opacity: 1, scale: 1, rotate: 0 }}
exit={{ opacity: 0, scale: 0.5, rotate: 45 }}
transition={{ duration: 0.2 }}
>
{prompt ? <Send size={18} /> : <Zap size={18} />}
</motion.div>
</AnimatePresence>
</motion.button>
</div>
</div>
</div>
{/* Tags section - Refined Oblivion Style */}
<div className="flex flex-wrap gap-2 px-1">
{["Create Image", "Data Analysis", "Research"].map((tag) => (
<motion.span
key={tag}
whileHover={{
scale: 1.05,
backgroundColor: "#222",
borderColor: "#444"
}}
className="px-3 py-1 text-[10px] font-bold uppercase tracking-wider text-zinc-400 bg-zinc-900 border border-zinc-800 rounded-lg cursor-pointer transition-all active:scale-95"
>
{tag}
</motion.span>
))}
</div>
</div>
)
}"use client"
import React, { useState } from "react"
import { motion, AnimatePresence } from "framer-motion"
import { Paperclip, Globe, Plus, Send, Zap } from "lucide-react"
import { cn } from "@/lib/utils"
export const componentMeta = {
name: "AI Narrative Input",
description: "A premium, physics-based AI prompt input with magnetic actions and fluid glassmorphism.",
props: {
placeholder: { type: "string", description: "The text area placeholder", default: '"Ask me anything..."' },
onSend: { type: "function", description: "Callback when the prompt is submitted" }
}
}
export default function AiNarrativeInput({
placeholder = "Ask me anything...",
onSend
}: {
placeholder?: string
onSend?: (prompt: string) => void
}) {
const [prompt, setPrompt] = useState("")
const [isFocused, setIsFocused] = useState(false)
const handleSend = () => {
if (prompt.trim()) {
onSend?.(prompt)
setPrompt("")
}
}
return (
<div className="flex flex-col w-full max-w-sm gap-4 font-sans antialiased">
{/* Container with Glassmorphism Border */}
<div
className={cn(
"relative group p-[1.5px] rounded-2xl transition-all duration-500",
isFocused
? "bg-gradient-to-br from-zinc-400 via-zinc-600 to-zinc-400 shadow-[0_0_20px_rgba(255,255,255,0.1)]"
: "bg-gradient-to-br from-zinc-600 to-zinc-900 shadow-xl"
)}
>
{/* Animated Corner Light */}
<div className="absolute top-[-10px] left-[-10px] w-12 h-12 bg-white/20 blur-xl pointer-events-none rounded-full" />
<div className="relative flex flex-col bg-black/90 backdrop-blur-xl rounded-[15px] overflow-hidden border border-white/5">
<textarea
value={prompt}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setPrompt(e.target.value)}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
placeholder={placeholder}
className="w-full h-24 bg-transparent border-none text-white text-sm p-4 resize-none outline-none placeholder:text-zinc-500 placeholder:transition-colors focus:placeholder:text-zinc-700 transition-all duration-300"
/>
<div className="flex items-center justify-between p-3 pt-0">
{/* Tool Buttons */}
<div className="flex gap-2">
{[Paperclip, Plus, Globe].map((Icon, i) => (
<motion.button
key={i}
whileHover={{ y: -3, color: "#fff" }}
whileTap={{ scale: 0.9 }}
className="p-2 text-zinc-600 transition-colors"
>
<Icon size={18} />
</motion.button>
))}
</div>
{/* Submit Button */}
<motion.button
onClick={handleSend}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className={cn(
"group relative flex items-center justify-center w-10 h-10 rounded-xl bg-zinc-800 border border-white/10 transition-all duration-300",
prompt ? "bg-white text-black shadow-[0_0_15px_rgba(255,255,255,0.3)]" : "text-zinc-600"
)}
>
<AnimatePresence mode="wait">
<motion.div
key={prompt ? "send" : "zap"}
initial={{ opacity: 0, scale: 0.5, rotate: -45 }}
animate={{ opacity: 1, scale: 1, rotate: 0 }}
exit={{ opacity: 0, scale: 0.5, rotate: 45 }}
transition={{ duration: 0.2 }}
>
{prompt ? <Send size={18} /> : <Zap size={18} />}
</motion.div>
</AnimatePresence>
</motion.button>
</div>
</div>
</div>
{/* Tags section - Refined Oblivion Style */}
<div className="flex flex-wrap gap-2 px-1">
{["Create Image", "Data Analysis", "Research"].map((tag) => (
<motion.span
key={tag}
whileHover={{
scale: 1.05,
backgroundColor: "#222",
borderColor: "#444"
}}
className="px-3 py-1 text-[10px] font-bold uppercase tracking-wider text-zinc-400 bg-zinc-900 border border-zinc-800 rounded-lg cursor-pointer transition-all active:scale-95"
>
{tag}
</motion.span>
))}
</div>
</div>
)
}Save to favoritesCopy to Figma
React
AI Narrative Input
A premium, physics-based AI prompt input with magnetic actions and fluid glassmorphism.
Installation
$ npx @cosmoo/oblivion add ai-narrative-inputDependencies
npm install framer-motion lucide-reactUsage Example
tsx
import AiNarrativeInput from "@/components/oblivion/ai-narrative-input"
export default function Demo() {
return (
<div className="p-10 flex justify-center">
<AiNarrativeInput />
</div>
)
}import AiNarrativeInput from "@/components/oblivion/ai-narrative-input"
export default function Demo() {
return (
<div className="p-10 flex justify-center">
<AiNarrativeInput />
</div>
)
}