mirror of
https://github.com/hanzoai/shadcn.git
synced 2026-11-03 03:18:52 +00:00
- Updated build-registry.mts to include all component types (ui, ai, 3d, animation, code, etc.) - Fixed missing components issue by processing all components that start with 'components:' - Now properly handles components that only exist in one theme variant - Registry now contains 363 components (up from 117) - AI, 3D, animation, and code components are now accessible via the registry
14 lines
15 KiB
JSON
14 lines
15 KiB
JSON
{
|
|
"name": "timeline",
|
|
"description": "Animated timeline component",
|
|
"dependencies": [
|
|
"motion"
|
|
],
|
|
"files": [
|
|
{
|
|
"name": "timeline.tsx",
|
|
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Check, Circle, Clock } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface TimelineItem {\n id: string\n title: string\n description?: string\n date?: string\n time?: string\n status?: \"completed\" | \"active\" | \"pending\"\n icon?: React.ReactNode\n content?: React.ReactNode\n}\n\ninterface TimelineProps extends React.HTMLAttributes<HTMLDivElement> {\n items: TimelineItem[]\n orientation?: \"vertical\" | \"horizontal\"\n variant?: \"default\" | \"alternate\" | \"compact\" | \"simple\"\n animated?: boolean\n}\n\nconst Timeline = React.forwardRef<HTMLDivElement, TimelineProps>(\n (\n {\n className,\n items,\n orientation = \"vertical\",\n variant = \"default\",\n animated = true,\n ...props\n },\n ref\n ) => {\n const [visibleItems, setVisibleItems] = React.useState<Set<string>>(\n new Set()\n )\n\n React.useEffect(() => {\n if (!animated) {\n setVisibleItems(new Set(items.map((item) => item.id)))\n return\n }\n\n const observer = new IntersectionObserver(\n (entries) => {\n entries.forEach((entry) => {\n if (entry.isIntersecting) {\n const id = entry.target.getAttribute(\"data-timeline-id\")\n if (id) {\n setVisibleItems((prev) => new Set([...prev, id]))\n }\n }\n })\n },\n {\n threshold: 0.1,\n rootMargin: \"50px\",\n }\n )\n\n const elements = document.querySelectorAll(\"[data-timeline-id]\")\n elements.forEach((el) => observer.observe(el))\n\n return () => observer.disconnect()\n }, [items, animated])\n\n const getStatusIcon = (status?: TimelineItem[\"status\"]) => {\n switch (status) {\n case \"completed\":\n return <Check className=\"h-3 w-3\" />\n case \"active\":\n return <Clock className=\"h-3 w-3 animate-pulse\" />\n default:\n return null\n }\n }\n\n const getStatusColor = (status?: TimelineItem[\"status\"]) => {\n switch (status) {\n case \"completed\":\n return \"bg-green-500 text-white\"\n case \"active\":\n return \"bg-blue-500 text-white\"\n case \"pending\":\n return \"bg-gray-300 dark:bg-gray-600\"\n default:\n return \"bg-primary text-primary-foreground\"\n }\n }\n\n if (orientation === \"horizontal\") {\n return (\n <div\n ref={ref}\n className={cn(\"relative overflow-x-auto\", className)}\n {...props}\n >\n <div className=\"flex items-start space-x-8 pb-4\">\n {items.map((item, index) => (\n <div\n key={item.id}\n data-timeline-id={item.id}\n className={cn(\n \"relative flex flex-col items-center min-w-[200px]\",\n animated &&\n !visibleItems.has(item.id) &&\n \"opacity-0 translate-y-4\",\n animated &&\n visibleItems.has(item.id) &&\n \"opacity-100 translate-y-0\",\n \"transition-all duration-500 ease-out\"\n )}\n >\n {/* Connector Line */}\n {index < items.length - 1 && (\n <div className=\"absolute top-5 left-[50%] w-[calc(100%+2rem)] h-[2px] bg-border\" />\n )}\n\n {/* Milestone */}\n <div\n className={cn(\n \"relative z-10 flex h-10 w-10 items-center justify-center rounded-full border-2 border-background shadow-sm\",\n getStatusColor(item.status)\n )}\n >\n {item.icon || getStatusIcon(item.status) || (\n <Circle className=\"h-3 w-3\" />\n )}\n </div>\n\n {/* Content */}\n <div className=\"mt-4 text-center\">\n {(item.date || item.time) && (\n <div className=\"mb-1 text-xs text-muted-foreground\">\n {item.date}\n {item.date && item.time && \" • \"}\n {item.time}\n </div>\n )}\n <h3 className=\"font-semibold\">{item.title}</h3>\n {item.description && (\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {item.description}\n </p>\n )}\n {item.content && <div className=\"mt-3\">{item.content}</div>}\n </div>\n </div>\n ))}\n </div>\n </div>\n )\n }\n\n // Vertical orientation\n if (variant === \"alternate\") {\n return (\n <div ref={ref} className={cn(\"relative\", className)} {...props}>\n {items.map((item, index) => (\n <div\n key={item.id}\n data-timeline-id={item.id}\n className={cn(\n \"relative flex items-start mb-8\",\n index % 2 === 0 ? \"justify-start\" : \"justify-end\",\n animated && !visibleItems.has(item.id) && \"opacity-0\",\n animated && visibleItems.has(item.id) && \"opacity-100\",\n animated &&\n !visibleItems.has(item.id) &&\n (index % 2 === 0 ? \"-translate-x-8\" : \"translate-x-8\"),\n animated && visibleItems.has(item.id) && \"translate-x-0\",\n \"transition-all duration-500 ease-out\"\n )}\n >\n {/* Center line */}\n <div className=\"absolute left-1/2 -translate-x-1/2 top-5 bottom-0 w-[2px] bg-border\" />\n\n {/* Content */}\n <div\n className={cn(\n \"relative w-[calc(50%-2rem)]\",\n index % 2 === 0 ? \"pr-8 text-right\" : \"pl-8 text-left\"\n )}\n >\n {/* Milestone */}\n <div\n className={cn(\n \"absolute top-0\",\n index % 2 === 0 ? \"right-0\" : \"left-0\",\n \"flex h-10 w-10 items-center justify-center rounded-full border-2 border-background shadow-sm\",\n getStatusColor(item.status)\n )}\n >\n {item.icon || getStatusIcon(item.status) || (\n <Circle className=\"h-3 w-3\" />\n )}\n </div>\n\n {/* Card */}\n <div\n className={cn(\n \"rounded-lg border bg-card p-4 shadow-sm\",\n index % 2 === 0 ? \"mr-12\" : \"ml-12\"\n )}\n >\n {(item.date || item.time) && (\n <div className=\"mb-2 text-xs text-muted-foreground\">\n {item.date}\n {item.date && item.time && \" • \"}\n {item.time}\n </div>\n )}\n <h3 className=\"font-semibold\">{item.title}</h3>\n {item.description && (\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {item.description}\n </p>\n )}\n {item.content && <div className=\"mt-3\">{item.content}</div>}\n </div>\n </div>\n </div>\n ))}\n </div>\n )\n }\n\n if (variant === \"compact\") {\n return (\n <div ref={ref} className={cn(\"relative\", className)} {...props}>\n <div className=\"space-y-2\">\n {items.map((item, index) => (\n <div\n key={item.id}\n data-timeline-id={item.id}\n className={cn(\n \"relative flex items-center gap-4 p-2 rounded-lg hover:bg-accent transition-colors\",\n animated &&\n !visibleItems.has(item.id) &&\n \"opacity-0 translate-x-4\",\n animated &&\n visibleItems.has(item.id) &&\n \"opacity-100 translate-x-0\",\n \"transition-all duration-500 ease-out\",\n `transition-delay-[${index * 50}ms]`\n )}\n >\n {/* Connector */}\n {index < items.length - 1 && (\n <div className=\"absolute left-5 top-10 bottom-0 w-[2px] bg-border\" />\n )}\n\n {/* Milestone */}\n <div\n className={cn(\n \"relative z-10 flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full\",\n getStatusColor(item.status)\n )}\n >\n {getStatusIcon(item.status) || <Circle className=\"h-2 w-2\" />}\n </div>\n\n {/* Content */}\n <div className=\"flex-1 flex items-center justify-between\">\n <div>\n <span className=\"font-medium text-sm\">{item.title}</span>\n {item.description && (\n <span className=\"ml-2 text-sm text-muted-foreground\">\n {item.description}\n </span>\n )}\n </div>\n {(item.date || item.time) && (\n <div className=\"text-xs text-muted-foreground\">\n {item.date} {item.time}\n </div>\n )}\n </div>\n </div>\n ))}\n </div>\n </div>\n )\n }\n\n if (variant === \"simple\") {\n return (\n <div ref={ref} className={cn(\"relative\", className)} {...props}>\n <div className=\"border-l-2 border-border pl-6 space-y-6\">\n {items.map((item, index) => (\n <div\n key={item.id}\n data-timeline-id={item.id}\n className={cn(\n \"relative\",\n animated &&\n !visibleItems.has(item.id) &&\n \"opacity-0 translate-x-4\",\n animated &&\n visibleItems.has(item.id) &&\n \"opacity-100 translate-x-0\",\n \"transition-all duration-500 ease-out\"\n )}\n >\n {/* Milestone */}\n <div\n className={cn(\n \"absolute -left-[1.75rem] flex h-4 w-4 items-center justify-center rounded-full border-2 border-background\",\n getStatusColor(item.status)\n )}\n >\n {item.status === \"completed\" && <Check className=\"h-2 w-2\" />}\n {item.status === \"active\" && (\n <div className=\"h-2 w-2 rounded-full bg-white animate-pulse\" />\n )}\n </div>\n\n {/* Content */}\n <div>\n {(item.date || item.time) && (\n <div className=\"mb-1 text-xs text-muted-foreground\">\n {item.date}\n {item.date && item.time && \" • \"}\n {item.time}\n </div>\n )}\n <h3 className=\"font-semibold\">{item.title}</h3>\n {item.description && (\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {item.description}\n </p>\n )}\n {item.content && <div className=\"mt-3\">{item.content}</div>}\n </div>\n </div>\n ))}\n </div>\n </div>\n )\n }\n\n // Default variant\n return (\n <div ref={ref} className={cn(\"relative\", className)} {...props}>\n <div className=\"space-y-8\">\n {items.map((item, index) => (\n <div\n key={item.id}\n data-timeline-id={item.id}\n className={cn(\n \"relative flex gap-6\",\n animated &&\n !visibleItems.has(item.id) &&\n \"opacity-0 translate-x-8\",\n animated &&\n visibleItems.has(item.id) &&\n \"opacity-100 translate-x-0\",\n \"transition-all duration-500 ease-out\"\n )}\n >\n {/* Connector Line */}\n {index < items.length - 1 && (\n <div className=\"absolute left-5 top-10 bottom-0 w-[2px] bg-border\" />\n )}\n\n {/* Milestone */}\n <div\n className={cn(\n \"relative z-10 flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full border-2 border-background shadow-sm\",\n getStatusColor(item.status)\n )}\n >\n {item.icon || getStatusIcon(item.status) || (\n <Circle className=\"h-3 w-3\" />\n )}\n </div>\n\n {/* Content */}\n <div className=\"flex-1 pb-8\">\n <div className=\"rounded-lg border bg-card p-4 shadow-sm\">\n {(item.date || item.time) && (\n <div className=\"mb-2 text-xs text-muted-foreground\">\n {item.date}\n {item.date && item.time && \" • \"}\n {item.time}\n </div>\n )}\n <h3 className=\"font-semibold\">{item.title}</h3>\n {item.description && (\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {item.description}\n </p>\n )}\n {item.content && <div className=\"mt-3\">{item.content}</div>}\n </div>\n </div>\n </div>\n ))}\n </div>\n </div>\n )\n }\n)\n\nTimeline.displayName = \"Timeline\"\n\nexport { Timeline, type TimelineItem, type TimelineProps }\n"
|
|
}
|
|
],
|
|
"type": "components:animation"
|
|
} |