feat: 添加产品经理和全栈开发角色资源文件
初始化产品经理和全栈开发角色的相关资源文件,包括角色定义、知识库、思维模式和执行流程文档
This commit is contained in:
91
docs/ui/components/photo-gallery.tsx
Normal file
91
docs/ui/components/photo-gallery.tsx
Normal file
@ -0,0 +1,91 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import Image from "next/image"
|
||||
import { Card } from "@/components/ui/card"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Calendar, MapPin } from "lucide-react"
|
||||
|
||||
interface Photo {
|
||||
id: number
|
||||
src: string
|
||||
title: string
|
||||
description: string
|
||||
category: string
|
||||
tags: string[]
|
||||
date: string
|
||||
exif: {
|
||||
camera: string
|
||||
lens: string
|
||||
settings: string
|
||||
location: string
|
||||
}
|
||||
}
|
||||
|
||||
interface PhotoGalleryProps {
|
||||
photos: Photo[]
|
||||
onPhotoClick: (photo: Photo) => void
|
||||
}
|
||||
|
||||
export function PhotoGallery({ photos, onPhotoClick }: PhotoGalleryProps) {
|
||||
const [loadedImages, setLoadedImages] = useState<Set<number>>(new Set())
|
||||
|
||||
const handleImageLoad = (photoId: number) => {
|
||||
setLoadedImages((prev) => new Set(prev).add(photoId))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
{photos.map((photo) => (
|
||||
<Card
|
||||
key={photo.id}
|
||||
className="group cursor-pointer overflow-hidden border-0 shadow-sm hover:shadow-xl transition-all duration-300 hover:scale-[1.02]"
|
||||
onClick={() => onPhotoClick(photo)}
|
||||
>
|
||||
<div className="relative aspect-[4/3] overflow-hidden">
|
||||
{!loadedImages.has(photo.id) && <div className="absolute inset-0 bg-gray-100 animate-pulse" />}
|
||||
<Image
|
||||
src={photo.src || "/placeholder.svg"}
|
||||
alt={photo.title}
|
||||
fill
|
||||
className={`object-cover transition-all duration-500 group-hover:scale-110 ${
|
||||
loadedImages.has(photo.id) ? "opacity-100" : "opacity-0"
|
||||
}`}
|
||||
onLoad={() => handleImageLoad(photo.id)}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-all duration-300" />
|
||||
|
||||
{/* Hover overlay */}
|
||||
<div className="absolute inset-0 p-4 flex flex-col justify-end opacity-0 group-hover:opacity-100 transition-all duration-300">
|
||||
<div className="text-white">
|
||||
<h3 className="font-medium text-lg mb-1">{photo.title}</h3>
|
||||
<p className="text-sm text-white/80 mb-2">{photo.description}</p>
|
||||
<div className="flex items-center gap-2 text-xs text-white/70">
|
||||
<Calendar className="h-3 w-3" />
|
||||
<span>{photo.date}</span>
|
||||
<MapPin className="h-3 w-3 ml-2" />
|
||||
<span>{photo.exif.location}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Card content */}
|
||||
<div className="p-4">
|
||||
<h3 className="font-medium text-gray-900 mb-2">{photo.title}</h3>
|
||||
<div className="flex flex-wrap gap-1 mb-3">
|
||||
{photo.tags.slice(0, 2).map((tag) => (
|
||||
<Badge key={tag} variant="secondary" className="text-xs">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
{photo.exif.camera} • {photo.exif.settings}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user