Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 5s
Build & Deploy / 🧪 QA (push) Failing after 1m31s
Build & Deploy / 🏗️ Build (push) Failing after 3m51s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
99 lines
3.6 KiB
TypeScript
99 lines
3.6 KiB
TypeScript
"use client";
|
|
import * as React from "react";
|
|
import { motion, AnimatePresence } from "framer-motion";
|
|
import { cn } from "../../utils/cn";
|
|
import { Search } from "lucide-react";
|
|
|
|
interface BlogCommandBarProps {
|
|
searchQuery: string;
|
|
onSearchChange: (value: string) => void;
|
|
tags: string[];
|
|
activeTags: string[];
|
|
onTagToggle: (tag: string) => void;
|
|
className?: string;
|
|
}
|
|
|
|
export const BlogCommandBar: React.FC<BlogCommandBarProps> = ({
|
|
searchQuery,
|
|
onSearchChange,
|
|
tags,
|
|
activeTags,
|
|
onTagToggle,
|
|
className = "",
|
|
}) => {
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"w-full max-w-2xl mx-auto flex flex-col items-center space-y-4 md:space-y-6 px-0",
|
|
className,
|
|
)}
|
|
>
|
|
{/* Command Input Area */}
|
|
<div className="relative group w-full px-0 sm:px-4 md:px-0">
|
|
{/* Glow Effect */}
|
|
<div className="absolute -inset-0.5 bg-gradient-to-r from-slate-200 to-slate-100 rounded-2xl blur opacity-20 group-hover:opacity-100 transition duration-500" />
|
|
|
|
<div className="relative flex items-center bg-white rounded-2xl border border-slate-200 p-1.5 md:p-2 shadow-sm transition-all focus-within:ring-2 focus-within:ring-slate-100 focus-within:border-slate-300">
|
|
<div className="pl-3 md:pl-4 text-slate-400">
|
|
<Search className="w-4 h-4 md:w-5 md:h-5" />
|
|
</div>
|
|
<input
|
|
type="text"
|
|
value={searchQuery}
|
|
onChange={(e) => onSearchChange(e.target.value)}
|
|
placeholder="Beiträge suchen..."
|
|
className="w-full bg-transparent px-3 md:px-4 py-2 md:py-3 text-base md:text-lg text-slate-900 placeholder:text-slate-300 outline-none font-bold"
|
|
/>
|
|
<AnimatePresence>
|
|
{searchQuery && (
|
|
<motion.button
|
|
initial={{ opacity: 0, scale: 0.8, x: 10 }}
|
|
animate={{ opacity: 1, scale: 1, x: 0 }}
|
|
exit={{ opacity: 0, scale: 0.8, x: 10 }}
|
|
onClick={() => onSearchChange("")}
|
|
className="mr-2 px-2.5 py-1 md:px-3 md:py-1.5 bg-slate-100 hover:bg-slate-200 rounded-lg text-[9px] md:text-[10px] font-bold uppercase tracking-wider text-slate-500 hover:text-slate-900 transition-colors"
|
|
>
|
|
Leeren
|
|
</motion.button>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Tag Command Row */}
|
|
<motion.div
|
|
layout
|
|
className="flex flex-wrap items-center justify-center gap-1.5 md:gap-2 px-4 md:px-0"
|
|
>
|
|
{tags.map((tag) => {
|
|
const isActive = activeTags.includes(tag);
|
|
return (
|
|
<motion.button
|
|
key={tag}
|
|
layout
|
|
onClick={() => onTagToggle(tag)}
|
|
className={cn(
|
|
"relative px-2.5 py-1 md:px-3 md:py-1.5 rounded-lg text-[9px] md:text-[10px] font-mono uppercase tracking-wider border transition-all duration-200 select-none overflow-hidden",
|
|
isActive
|
|
? "text-white border-slate-900 shadow-md"
|
|
: "bg-white text-slate-500 border-slate-200 hover:border-slate-400 hover:text-slate-900 hover:bg-slate-50",
|
|
)}
|
|
whileHover={{ scale: 1.02 }}
|
|
whileTap={{ scale: 0.98 }}
|
|
>
|
|
<span className="relative z-10">#{tag}</span>
|
|
{isActive && (
|
|
<motion.div
|
|
layoutId="activeTag"
|
|
className="absolute inset-0 bg-slate-900 z-0"
|
|
transition={{ type: "spring", bounce: 0.2, duration: 0.6 }}
|
|
/>
|
|
)}
|
|
</motion.button>
|
|
);
|
|
})}
|
|
</motion.div>
|
|
</div>
|
|
);
|
|
};
|