Sunflower UI
Search
Copy Code
Light Mode

Full-text search for the docs

Index every page and put it behind ⌘K. Results are ranked by heading before body and cached at the edge.

To-dos
6
Extract text from every doc page
Build the search index
Add the ⌘K search endpoint
Auto Approve in 10s
plan-card.tsx
globals.css
prompt.md
Collapse
Reset
Copy Code
Download
Run
1
"use client";
2
3
import React, { useState, useEffect } from 'react';
4
import { motion, AnimatePresence } from 'framer-motion';
5
import { Download, ListChecks, CircleDashed, MoreHorizontal, Minus, Command, CornerDownLeft } from 'lucide-react';
6
import { cn } from "@/lib/utils";
7
8
const TODOS = [
9
"Extract text from every doc page",
10
"Build the search index",
11
"Add the ⌘K search endpoint",
12
"Rank by heading, then body",
13
"Cache results at the edge",
14
"Reindex on every deploy",
15
];
16
17
export function PlanCard({ className }: { className?: string }) {
18
const [isExpanded, setIsExpanded] = useState(false);
19
const [timeLeft, setTimeLeft] = useState(10);
20
21
useEffect(() => {
22
const timer = setInterval(() => {
23
setTimeLeft(t => (t <= 0 ? 10 : t - 1));
24
}, 1000);
25
return () => clearInterval(timer);
26
}, []);
27
28
const progress = timeLeft / 10;
29
const timerColor = timeLeft > 3 ? '#10B981' : '#EF4444'; // Emerald for >3s, Red for <=3s
30
31
return (
32
<div className={cn("w-full max-w-[520px] bg-white dark:bg-[#151515] rounded-[20px] border border-black/5 dark:border-white/10 shadow-sm dark:shadow-none p-5 font-sans", className)}>
33
34
{/* Header */}
35
<div className="flex items-start justify-between">
36
<div className="flex items-center gap-3">
37
<div className="w-8 h-8 rounded-lg bg-amber-50 dark:bg-[#3A2810] border border-amber-200/50 dark:border-[#5A3F1B] flex items-center justify-center shrink-0">
38
<ListChecks className="w-4 h-4 text-amber-600 dark:text-[#F59E0B]" />
39
</div>
40
<h2 className="text-zinc-900 dark:text-white font-semibold text-[17px]">Full-text search for the docs</h2>
41
</div>
42
<button className="text-zinc-400 dark:text-zinc-400 hover:text-zinc-600 dark:hover:text-white transition-colors p-1">
43
<Download className="w-5 h-5" />
44
</button>
45
</div>
46
47
{/* Description */}
48
<p className="text-zinc-500 dark:text-zinc-400 text-[15px] leading-relaxed mt-4">
49
Index every page and put it behind ⌘K. Results are ranked by heading before body and cached at the edge.
50
</p>
51
52
{/* To-dos Section */}
53
<div className="mt-5 bg-zinc-50 dark:bg-[#1F1F1F] rounded-[16px] overflow-hidden border border-black/[0.03] dark:border-transparent">
54
<div className="p-4">
55
<div className="flex items-center justify-between mb-4">
56
<div className="flex items-center gap-2 text-zinc-700 dark:text-zinc-300">
57
<ListChecks className="w-[18px] h-[18px]" />
58
<span className="font-medium text-[15px]">To-dos</span>
59
</div>
60
<span className="text-zinc-400 dark:text-zinc-500 font-medium text-[14px]">{TODOS.length}</span>
61
</div>
62
63
<div className="flex flex-col gap-3.5">
64
{TODOS.slice(0, 3).map((todo, idx) => (
65
<div key={idx} className="flex items-center gap-3 text-zinc-600 dark:text-zinc-400">
66
<CircleDashed className="w-[18px] h-[18px] text-zinc-300 dark:text-zinc-600 shrink-0" />
67
<span className="text-[14px]">{todo}</span>
68
</div>
69
))}
70
71
<AnimatePresence initial={false}>
72
{isExpanded && (
73
<motion.div
74
initial={{ height: 0, opacity: 0 }}
75
animate={{ height: 'auto', opacity: 1 }}
76
exit={{ height: 0, opacity: 0 }}
77
transition={{ duration: 0.3, ease: "easeInOut" }}
78
className="flex flex-col gap-3.5 overflow-hidden"
79
>
80
{TODOS.slice(3).map((todo, idx) => (
81
<div key={idx + 3} className="flex items-center gap-3 text-zinc-600 dark:text-zinc-400 pt-[2px]">
82
<CircleDashed className="w-[18px] h-[18px] text-zinc-300 dark:text-zinc-600 shrink-0" />
83
<span className="text-[14px]">{todo}</span>
84
</div>
85
))}
86
<button
87
onClick={() => setIsExpanded(false)}
88
className="flex items-center gap-3 text-zinc-400 dark:text-zinc-500 hover:text-zinc-600 dark:hover:text-zinc-300 transition-colors mt-1"
89
>
90
<Minus className="w-[18px] h-[18px] shrink-0" />
91
<span className="text-[14px]">Show less</span>
92
</button>
93
</motion.div>
94
)}
95
</AnimatePresence>
96
97
{!isExpanded && (
98
<button
99
onClick={() => setIsExpanded(true)}
100
className="flex items-center gap-3 text-zinc-400 dark:text-zinc-500 hover:text-zinc-600 dark:hover:text-zinc-300 transition-colors"
101
>
102
<MoreHorizontal className="w-[18px] h-[18px] shrink-0" />
103
<span className="text-[14px]">3 more</span>
104
</button>
105
)}
106
</div>
107
</div>
108
</div>
109
110
{/* Footer */}
111
<div className="flex items-center justify-between mt-5 pt-1">
112
<div className="flex items-center gap-2.5">
113
<div className="relative w-4 h-4 flex items-center justify-center">
114
<svg className="w-4 h-4 transform -rotate-90" viewBox="0 0 16 16">
115
<circle
116
cx="8"
117
cy="8"
118
r="7"
119
fill="none"
120
className="stroke-zinc-200 dark:stroke-[#333]"
121
strokeWidth="2"
122
/>
123
<motion.circle
124
cx="8"
125
cy="8"
126
r="7"
127
fill="none"
128
stroke={timerColor}
129
strokeWidth="2"
130
strokeDasharray="44"
131
strokeDashoffset={44 * (1 - progress)}
132
strokeLinecap="round"
133
animate={{ strokeDashoffset: 44 * (1 - progress), stroke: timerColor }}
134
transition={{ duration: timeLeft === 10 ? 0 : 1, ease: "linear" }}
135
/>
136
</svg>
137
</div>
138
<span className="text-zinc-500 dark:text-zinc-400 text-[14px]">Auto Approve in {timeLeft}s</span>
139
</div>
140
141
<div className="flex items-center gap-2.5">
142
<button
143
onClick={() => setIsExpanded(!isExpanded)}
144
className="px-4 py-2 rounded-lg bg-black/5 dark:bg-white/5 hover:bg-black/10 dark:hover:bg-white/10 text-zinc-700 dark:text-zinc-300 text-[14px] font-medium transition-colors"
145
>
146
{isExpanded ? 'Hide Plan' : 'View Plan'}
147
</button>
148
149
<button className="flex items-center gap-2 px-3 py-2 rounded-lg bg-black dark:bg-white hover:bg-zinc-800 dark:hover:bg-zinc-100 text-white dark:text-black text-[14px] font-medium transition-colors">
150
Approve
151
<div className="flex items-center gap-1">
152
<div className="flex items-center justify-center w-5 h-5 rounded-[4px] bg-white/20 dark:bg-black/10">
153
<Command className="w-3 h-3 text-white/80 dark:text-black/60" />
154
</div>
155
<div className="flex items-center justify-center w-5 h-5 rounded-[4px] bg-white/20 dark:bg-black/10">
156
<CornerDownLeft className="w-3 h-3 text-white/80 dark:text-black/60" />
157
</div>
158
</div>
159
</button>
160
</div>
161
</div>
162
163
</div>
164
);
165
}
166