|
| 1 | +"use client"; |
| 2 | + |
| 3 | +import * as React from "react"; |
| 4 | +import { |
| 5 | + VersionSelector, |
| 6 | + CompactVersionSelector, |
| 7 | + formatVersionTime, |
| 8 | + type Version, |
| 9 | +} from "@/components/ui/version-selector"; |
| 10 | +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; |
| 11 | +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; |
| 12 | +import { Badge } from "@/components/ui/badge"; |
| 13 | +import { toast } from "sonner"; |
| 14 | + |
| 15 | +// Generate mock versions with realistic data |
| 16 | +function generateMockVersions(count: number): Version[] { |
| 17 | + const versions: Version[] = []; |
| 18 | + const now = Date.now(); |
| 19 | + |
| 20 | + for (let i = count; i > 0; i--) { |
| 21 | + // Create versions with varying time intervals |
| 22 | + let createdAt: number; |
| 23 | + if (i === count) { |
| 24 | + // Current version - 2 hours ago |
| 25 | + createdAt = now - 2 * 60 * 60 * 1000; |
| 26 | + } else if (i === count - 1) { |
| 27 | + // Previous version - 1 day ago |
| 28 | + createdAt = now - 24 * 60 * 60 * 1000; |
| 29 | + } else if (i === count - 2) { |
| 30 | + // 3 days ago |
| 31 | + createdAt = now - 3 * 24 * 60 * 60 * 1000; |
| 32 | + } else if (i === count - 3) { |
| 33 | + // 1 week ago |
| 34 | + createdAt = now - 7 * 24 * 60 * 60 * 1000; |
| 35 | + } else { |
| 36 | + // Older versions - weeks/months ago |
| 37 | + createdAt = now - (count - i + 7) * 24 * 60 * 60 * 1000; |
| 38 | + } |
| 39 | + |
| 40 | + versions.push({ |
| 41 | + version: i, |
| 42 | + created_at: new Date(createdAt).toISOString(), |
| 43 | + size: Math.floor(Math.random() * 5000) + 500, |
| 44 | + file_count: Math.floor(Math.random() * 5) + 1, |
| 45 | + edited_with_pin: i > 1 && Math.random() > 0.7, // 30% chance for non-original versions |
| 46 | + }); |
| 47 | + } |
| 48 | + |
| 49 | + return versions; |
| 50 | +} |
| 51 | + |
| 52 | +export default function VersionSelectorDemo() { |
| 53 | + const [currentVersion, setCurrentVersion] = React.useState(5); |
| 54 | + const [compactVersion, setCompactVersion] = React.useState(3); |
| 55 | + const [loadingDemo, setLoadingDemo] = React.useState(false); |
| 56 | + |
| 57 | + const fewVersions = generateMockVersions(3); |
| 58 | + const manyVersions = generateMockVersions(12); |
| 59 | + const singleVersion = generateMockVersions(1); |
| 60 | + |
| 61 | + const handleVersionChange = (version: number) => { |
| 62 | + setLoadingDemo(true); |
| 63 | + toast.info(`Loading version ${version}...`); |
| 64 | + |
| 65 | + setTimeout(() => { |
| 66 | + setCurrentVersion(version); |
| 67 | + setLoadingDemo(false); |
| 68 | + toast.success(`Switched to version ${version}`); |
| 69 | + }, 1000); |
| 70 | + }; |
| 71 | + |
| 72 | + return ( |
| 73 | + <div className="container mx-auto py-8"> |
| 74 | + <h1 className="mb-8 text-3xl font-bold">Version Selector Demo</h1> |
| 75 | + |
| 76 | + <Tabs defaultValue="standard" className="w-full"> |
| 77 | + <TabsList className="grid w-full grid-cols-4"> |
| 78 | + <TabsTrigger value="standard">Standard</TabsTrigger> |
| 79 | + <TabsTrigger value="compact">Compact</TabsTrigger> |
| 80 | + <TabsTrigger value="states">States</TabsTrigger> |
| 81 | + <TabsTrigger value="formatting">Time Format</TabsTrigger> |
| 82 | + </TabsList> |
| 83 | + |
| 84 | + <TabsContent value="standard" className="space-y-4"> |
| 85 | + <Card> |
| 86 | + <CardHeader> |
| 87 | + <CardTitle>Standard Version Selector</CardTitle> |
| 88 | + </CardHeader> |
| 89 | + <CardContent className="space-y-6"> |
| 90 | + <div> |
| 91 | + <h3 className="mb-3 text-lg font-semibold"> |
| 92 | + Multiple Versions (12 versions) |
| 93 | + </h3> |
| 94 | + <VersionSelector |
| 95 | + currentVersion={currentVersion} |
| 96 | + versions={manyVersions} |
| 97 | + onVersionChangeAction={handleVersionChange} |
| 98 | + loading={loadingDemo} |
| 99 | + /> |
| 100 | + <p className="text-muted-foreground mt-2 text-sm"> |
| 101 | + Currently viewing version {currentVersion} |
| 102 | + </p> |
| 103 | + </div> |
| 104 | + |
| 105 | + <div> |
| 106 | + <h3 className="mb-3 text-lg font-semibold"> |
| 107 | + Few Versions (3 versions) |
| 108 | + </h3> |
| 109 | + <VersionSelector |
| 110 | + currentVersion={2} |
| 111 | + versions={fewVersions} |
| 112 | + onVersionChangeAction={(v) => |
| 113 | + toast.info(`Selected version ${v}`) |
| 114 | + } |
| 115 | + /> |
| 116 | + </div> |
| 117 | + |
| 118 | + <div> |
| 119 | + <h3 className="mb-3 text-lg font-semibold"> |
| 120 | + Single Version (no dropdown) |
| 121 | + </h3> |
| 122 | + <VersionSelector |
| 123 | + currentVersion={1} |
| 124 | + versions={singleVersion} |
| 125 | + onVersionChangeAction={(v) => |
| 126 | + toast.info(`Selected version ${v}`) |
| 127 | + } |
| 128 | + /> |
| 129 | + </div> |
| 130 | + |
| 131 | + <div> |
| 132 | + <h3 className="mb-3 text-lg font-semibold">No Versions</h3> |
| 133 | + <VersionSelector |
| 134 | + currentVersion={1} |
| 135 | + versions={[]} |
| 136 | + onVersionChangeAction={(v) => |
| 137 | + toast.info(`Selected version ${v}`) |
| 138 | + } |
| 139 | + /> |
| 140 | + <p className="text-muted-foreground text-sm"> |
| 141 | + Returns null when no versions |
| 142 | + </p> |
| 143 | + </div> |
| 144 | + </CardContent> |
| 145 | + </Card> |
| 146 | + </TabsContent> |
| 147 | + |
| 148 | + <TabsContent value="compact" className="space-y-4"> |
| 149 | + <Card> |
| 150 | + <CardHeader> |
| 151 | + <CardTitle>Compact Version Selector</CardTitle> |
| 152 | + </CardHeader> |
| 153 | + <CardContent className="space-y-6"> |
| 154 | + <div> |
| 155 | + <h3 className="mb-3 text-lg font-semibold"> |
| 156 | + Compact Mode (Mobile-friendly) |
| 157 | + </h3> |
| 158 | + <CompactVersionSelector |
| 159 | + currentVersion={compactVersion} |
| 160 | + versions={manyVersions.slice(0, 5)} |
| 161 | + onVersionChangeAction={setCompactVersion} |
| 162 | + /> |
| 163 | + <p className="text-muted-foreground mt-2 text-sm"> |
| 164 | + Currently viewing version {compactVersion} |
| 165 | + </p> |
| 166 | + </div> |
| 167 | + |
| 168 | + <div> |
| 169 | + <h3 className="mb-3 text-lg font-semibold"> |
| 170 | + Compact with Custom Styling |
| 171 | + </h3> |
| 172 | + <div className="flex gap-2"> |
| 173 | + <CompactVersionSelector |
| 174 | + currentVersion={2} |
| 175 | + versions={fewVersions} |
| 176 | + onVersionChangeAction={(v) => |
| 177 | + toast.info(`Selected version ${v}`) |
| 178 | + } |
| 179 | + className="w-[100px]" |
| 180 | + /> |
| 181 | + <span className="text-muted-foreground text-sm"> |
| 182 | + Custom width |
| 183 | + </span> |
| 184 | + </div> |
| 185 | + </div> |
| 186 | + </CardContent> |
| 187 | + </Card> |
| 188 | + </TabsContent> |
| 189 | + |
| 190 | + <TabsContent value="states" className="space-y-4"> |
| 191 | + <Card> |
| 192 | + <CardHeader> |
| 193 | + <CardTitle>Different States</CardTitle> |
| 194 | + </CardHeader> |
| 195 | + <CardContent className="space-y-6"> |
| 196 | + <div> |
| 197 | + <h3 className="mb-3 text-lg font-semibold">Loading State</h3> |
| 198 | + <VersionSelector |
| 199 | + currentVersion={3} |
| 200 | + versions={fewVersions} |
| 201 | + onVersionChangeAction={() => {}} |
| 202 | + loading={true} |
| 203 | + /> |
| 204 | + <p className="text-muted-foreground mt-2 text-sm"> |
| 205 | + Selector is disabled during loading |
| 206 | + </p> |
| 207 | + </div> |
| 208 | + |
| 209 | + <div> |
| 210 | + <h3 className="mb-3 text-lg font-semibold">Disabled State</h3> |
| 211 | + <VersionSelector |
| 212 | + currentVersion={3} |
| 213 | + versions={fewVersions} |
| 214 | + onVersionChangeAction={() => {}} |
| 215 | + disabled={true} |
| 216 | + /> |
| 217 | + </div> |
| 218 | + |
| 219 | + <div> |
| 220 | + <h3 className="mb-3 text-lg font-semibold"> |
| 221 | + With PIN-edited Versions |
| 222 | + </h3> |
| 223 | + <VersionSelector |
| 224 | + currentVersion={3} |
| 225 | + versions={[ |
| 226 | + ...fewVersions, |
| 227 | + { |
| 228 | + version: 4, |
| 229 | + created_at: new Date( |
| 230 | + Date.now() - 60 * 60 * 1000 |
| 231 | + ).toISOString(), |
| 232 | + size: 2048, |
| 233 | + file_count: 3, |
| 234 | + edited_with_pin: true, |
| 235 | + }, |
| 236 | + ]} |
| 237 | + onVersionChangeAction={(v) => |
| 238 | + toast.info(`Selected version ${v}`) |
| 239 | + } |
| 240 | + /> |
| 241 | + <p className="text-muted-foreground mt-2 text-sm"> |
| 242 | + Lock icon indicates PIN-protected edits |
| 243 | + </p> |
| 244 | + </div> |
| 245 | + </CardContent> |
| 246 | + </Card> |
| 247 | + </TabsContent> |
| 248 | + |
| 249 | + <TabsContent value="formatting" className="space-y-4"> |
| 250 | + <Card> |
| 251 | + <CardHeader> |
| 252 | + <CardTitle>Time Formatting Examples</CardTitle> |
| 253 | + </CardHeader> |
| 254 | + <CardContent className="space-y-4"> |
| 255 | + {[ |
| 256 | + { time: 30, label: "30 seconds ago" }, |
| 257 | + { time: 60, label: "1 minute ago" }, |
| 258 | + { time: 300, label: "5 minutes ago" }, |
| 259 | + { time: 3600, label: "1 hour ago" }, |
| 260 | + { time: 7200, label: "2 hours ago" }, |
| 261 | + { time: 86400, label: "Yesterday" }, |
| 262 | + { time: 259200, label: "3 days ago" }, |
| 263 | + { time: 604800, label: "1 week ago" }, |
| 264 | + { time: 2592000, label: "1 month ago" }, |
| 265 | + { time: 31536000, label: "1 year ago" }, |
| 266 | + ].map(({ time, label }) => { |
| 267 | + const timestamp = new Date( |
| 268 | + Date.now() - time * 1000 |
| 269 | + ).toISOString(); |
| 270 | + const formatted = formatVersionTime(timestamp); |
| 271 | + return ( |
| 272 | + <div key={time} className="flex items-center gap-4"> |
| 273 | + <Badge variant="outline" className="w-32"> |
| 274 | + {label} |
| 275 | + </Badge> |
| 276 | + <span className="font-mono text-sm">{formatted}</span> |
| 277 | + </div> |
| 278 | + ); |
| 279 | + })} |
| 280 | + </CardContent> |
| 281 | + </Card> |
| 282 | + </TabsContent> |
| 283 | + </Tabs> |
| 284 | + |
| 285 | + <Card className="mt-8"> |
| 286 | + <CardHeader> |
| 287 | + <CardTitle>Usage Example</CardTitle> |
| 288 | + </CardHeader> |
| 289 | + <CardContent> |
| 290 | + <pre className="bg-muted overflow-x-auto rounded-lg p-4 text-sm"> |
| 291 | + {`// Standard version selector |
| 292 | +<VersionSelector |
| 293 | + currentVersion={3} |
| 294 | + versions={versions} |
| 295 | + onVersionChangeAction={handleVersionChange} |
| 296 | + loading={isLoading} |
| 297 | +/> |
| 298 | +
|
| 299 | +// Compact version selector |
| 300 | +<CompactVersionSelector |
| 301 | + currentVersion={3} |
| 302 | + versions={versions} |
| 303 | + onVersionChangeAction={handleVersionChange} |
| 304 | +/> |
| 305 | +
|
| 306 | +// Version data structure |
| 307 | +const version: Version = { |
| 308 | + version: 3, |
| 309 | + created_at: "2025-01-15T10:30:00Z", |
| 310 | + size: 2048, |
| 311 | + file_count: 2, |
| 312 | + edited_with_pin: true |
| 313 | +}; |
| 314 | +
|
| 315 | +// Format timestamp |
| 316 | +const formatted = formatVersionTime(timestamp);`} |
| 317 | + </pre> |
| 318 | + </CardContent> |
| 319 | + </Card> |
| 320 | + |
| 321 | + <Card className="mt-8"> |
| 322 | + <CardHeader> |
| 323 | + <CardTitle>Features</CardTitle> |
| 324 | + </CardHeader> |
| 325 | + <CardContent className="space-y-2"> |
| 326 | + <p>✓ Dropdown showing version list with timestamps</p> |
| 327 | + <p>✓ Current version clearly highlighted</p> |
| 328 | + <p>✓ Human-readable relative timestamps</p> |
| 329 | + <p>✓ Shows version metadata (file count, PIN edits)</p> |
| 330 | + <p>✓ Maximum 50 versions support</p> |
| 331 | + <p>✓ Reverse chronological order (newest first)</p> |
| 332 | + <p>✓ Loading and disabled states</p> |
| 333 | + <p>✓ Mobile-friendly compact variant</p> |
| 334 | + <p>✓ Keyboard navigable</p> |
| 335 | + <p>✓ Accessible with ARIA labels</p> |
| 336 | + </CardContent> |
| 337 | + </Card> |
| 338 | + </div> |
| 339 | + ); |
| 340 | +} |
0 commit comments