mirror of
https://github.com/hanzoai/shadcn.git
synced 2026-11-03 03:18:52 +00:00
- Enable all 15 finance component docs (from .disabled → .mdx) - Create stock-market.mdx, 8 chart docs, 8 block docs, 2 framework docs - Add direction.tsx component (RTL/LTR support from upstream shadcn) - Add xs/icon-xs button sizes, data-variant/data-size attributes - Fix build-registry.mts to handle default exports in namespace re-exports - Add default props to order-entry, positions-list, orders-history, trading-panel - Fix 18 navigation stubs → re-export from root implementations - Add 44 orphaned MDX files to nav (installation, dark-mode, desktop, guides, etc) - Migrate 6 framer-motion imports → motion/react - Bump React 19.2.0→19.2.4, Next.js 16.0.1→16.1.6, motion→12.34.0 - Align lucide-react, sonner v2, tailwind-merge v3, postcss-selector-parser v7 - Remove framer-motion dep (superseded by motion) - Fix @types/react-dom, TypeScript versions across workspaces
14 lines
11 KiB
JSON
14 lines
11 KiB
JSON
{
|
|
"name": "apple-cards-carousel",
|
|
"description": "Apple-style cards carousel",
|
|
"dependencies": [
|
|
"motion"
|
|
],
|
|
"files": [
|
|
{
|
|
"name": "apple-cards-carousel.tsx",
|
|
"content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronLeft, ChevronRight } from \"lucide-react\"\nimport {\n AnimatePresence,\n motion,\n PanInfo,\n useMotionValue,\n useTransform,\n} from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\nimport { Button } from \"./button\"\nimport { Card } from \"./card\"\n\nexport interface CarouselCard {\n id: string\n title: string\n subtitle?: string\n description?: string\n image?: string\n gradient?: string\n content?: React.ReactNode\n}\n\ninterface AppleCardsCarouselProps {\n cards: CarouselCard[]\n autoPlay?: boolean\n autoPlayInterval?: number\n className?: string\n cardClassName?: string\n showArrows?: boolean\n showDots?: boolean\n parallaxOffset?: number\n stackOffset?: number\n stackScale?: number\n swipeThreshold?: number\n}\n\nexport function AppleCardsCarousel({\n cards,\n autoPlay = false,\n autoPlayInterval = 5000,\n className,\n cardClassName,\n showArrows = true,\n showDots = true,\n parallaxOffset = 50,\n stackOffset = 10,\n stackScale = 0.95,\n swipeThreshold = 50,\n}: AppleCardsCarouselProps) {\n const [currentIndex, setCurrentIndex] = React.useState(0)\n const [isAutoPlaying, setIsAutoPlaying] = React.useState(autoPlay)\n const containerRef = React.useRef<HTMLDivElement>(null)\n const dragX = useMotionValue(0)\n\n // Calculate card positions\n const getCardStyle = (index: number) => {\n const offset = index - currentIndex\n const absOffset = Math.abs(offset)\n const sign = Math.sign(offset)\n\n // Only show 3 cards at a time for performance\n if (absOffset > 2) {\n return {\n display: \"none\",\n x: 0,\n scale: 1,\n opacity: 0,\n zIndex: 0,\n }\n }\n\n const baseX = offset * stackOffset\n const scale = Math.pow(stackScale, absOffset)\n const opacity = Math.max(0, 1 - absOffset * 0.3)\n const zIndex = cards.length - absOffset\n\n return {\n display: \"block\",\n x: baseX,\n scale,\n opacity,\n zIndex,\n }\n }\n\n // Navigate to next card\n const goToNext = React.useCallback(() => {\n setCurrentIndex((prev) => (prev + 1) % cards.length)\n }, [cards.length])\n\n // Navigate to previous card\n const goToPrevious = React.useCallback(() => {\n setCurrentIndex((prev) => (prev - 1 + cards.length) % cards.length)\n }, [cards.length])\n\n // Navigate to specific card\n const goToCard = React.useCallback((index: number) => {\n setCurrentIndex(index)\n }, [])\n\n // Handle drag end\n const handleDragEnd = React.useCallback(\n (event: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => {\n const { offset, velocity } = info\n\n // Determine if we should navigate based on drag distance and velocity\n if (Math.abs(offset.x) > swipeThreshold || Math.abs(velocity.x) > 500) {\n if (offset.x > 0) {\n goToPrevious()\n } else {\n goToNext()\n }\n }\n },\n [goToNext, goToPrevious, swipeThreshold]\n )\n\n // Auto-play functionality\n React.useEffect(() => {\n if (!isAutoPlaying) return\n\n const interval = setInterval(goToNext, autoPlayInterval)\n\n return () => clearInterval(interval)\n }, [isAutoPlaying, goToNext, autoPlayInterval])\n\n // Pause auto-play on hover\n const handleMouseEnter = React.useCallback(() => {\n if (autoPlay) {\n setIsAutoPlaying(false)\n }\n }, [autoPlay])\n\n const handleMouseLeave = React.useCallback(() => {\n if (autoPlay) {\n setIsAutoPlaying(true)\n }\n }, [autoPlay])\n\n // Keyboard navigation\n React.useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"ArrowLeft\") {\n goToPrevious()\n } else if (e.key === \"ArrowRight\") {\n goToNext()\n }\n }\n\n window.addEventListener(\"keydown\", handleKeyDown)\n\n return () => window.removeEventListener(\"keydown\", handleKeyDown)\n }, [goToNext, goToPrevious])\n\n return (\n <div\n ref={containerRef}\n className={cn(\"relative w-full max-w-6xl mx-auto px-4 py-8\", className)}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n >\n {/* Cards Container */}\n <div className=\"relative h-[400px] md:h-[500px] lg:h-[600px] flex items-center justify-center\">\n <AnimatePresence mode=\"popLayout\">\n {cards.map((card, index) => {\n const style = getCardStyle(index)\n const isActive = index === currentIndex\n\n return (\n <motion.div\n key={card.id}\n className={cn(\n \"absolute inset-0 w-full h-full\",\n !isActive && \"pointer-events-none\"\n )}\n initial={false}\n animate={{\n x: style.x,\n scale: style.scale,\n opacity: style.opacity,\n zIndex: style.zIndex,\n }}\n transition={{\n type: \"spring\",\n stiffness: 300,\n damping: 30,\n }}\n drag={isActive ? \"x\" : false}\n dragSnapToOrigin\n dragElastic={0.2}\n onDragEnd={handleDragEnd}\n whileDrag={{ cursor: \"grabbing\" }}\n style={{\n x: dragX,\n display: style.display,\n }}\n >\n <Card\n className={cn(\n \"relative w-full h-full overflow-hidden cursor-grab\",\n \"bg-gradient-to-br from-white to-gray-50 dark:from-gray-900 dark:to-gray-950\",\n \"shadow-2xl hover:shadow-3xl transition-shadow duration-300\",\n cardClassName\n )}\n style={{\n background: card.gradient,\n }}\n >\n {/* Parallax Background Image */}\n {card.image && (\n <motion.div\n className=\"absolute inset-0 w-full h-full\"\n style={{\n x: useTransform(\n dragX,\n [-200, 0, 200],\n [parallaxOffset, 0, -parallaxOffset]\n ),\n }}\n >\n <img\n src={card.image}\n alt={card.title}\n className=\"w-full h-full object-cover\"\n />\n <div className=\"absolute inset-0 bg-gradient-to-t from-black/60 via-black/20 to-transparent\" />\n </motion.div>\n )}\n\n {/* Card Content */}\n <div className=\"relative z-10 flex flex-col justify-end h-full p-8 md:p-12\">\n {card.content ? (\n card.content\n ) : (\n <>\n <motion.h3\n className=\"text-3xl md:text-4xl lg:text-5xl font-bold text-white mb-2\"\n initial={{ opacity: 0, y: 20 }}\n animate={{ opacity: isActive ? 1 : 0.7, y: 0 }}\n transition={{ delay: 0.1 }}\n >\n {card.title}\n </motion.h3>\n\n {card.subtitle && (\n <motion.p\n className=\"text-xl md:text-2xl text-white/80 mb-4\"\n initial={{ opacity: 0, y: 20 }}\n animate={{ opacity: isActive ? 1 : 0.7, y: 0 }}\n transition={{ delay: 0.2 }}\n >\n {card.subtitle}\n </motion.p>\n )}\n\n {card.description && (\n <motion.p\n className=\"text-base md:text-lg text-white/70 max-w-2xl\"\n initial={{ opacity: 0, y: 20 }}\n animate={{ opacity: isActive ? 1 : 0.7, y: 0 }}\n transition={{ delay: 0.3 }}\n >\n {card.description}\n </motion.p>\n )}\n </>\n )}\n </div>\n </Card>\n </motion.div>\n )\n })}\n </AnimatePresence>\n </div>\n\n {/* Navigation Arrows */}\n {showArrows && (\n <>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className={cn(\n \"absolute left-4 top-1/2 -translate-y-1/2 z-20\",\n \"bg-white/10 backdrop-blur-md hover:bg-white/20\",\n \"text-white border border-white/20\",\n \"hidden md:flex\"\n )}\n onClick={goToPrevious}\n >\n <ChevronLeft className=\"h-6 w-6\" />\n <span className=\"sr-only\">Previous card</span>\n </Button>\n\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className={cn(\n \"absolute right-4 top-1/2 -translate-y-1/2 z-20\",\n \"bg-white/10 backdrop-blur-md hover:bg-white/20\",\n \"text-white border border-white/20\",\n \"hidden md:flex\"\n )}\n onClick={goToNext}\n >\n <ChevronRight className=\"h-6 w-6\" />\n <span className=\"sr-only\">Next card</span>\n </Button>\n </>\n )}\n\n {/* Dot Indicators */}\n {showDots && (\n <div className=\"absolute bottom-4 left-1/2 -translate-x-1/2 z-20 flex gap-2\">\n {cards.map((_, index) => (\n <button\n key={index}\n className={cn(\n \"w-2 h-2 rounded-full transition-all duration-300\",\n \"bg-white/40 hover:bg-white/60\",\n index === currentIndex && \"w-8 bg-white\"\n )}\n onClick={() => goToCard(index)}\n >\n <span className=\"sr-only\">Go to card {index + 1}</span>\n </button>\n ))}\n </div>\n )}\n </div>\n )\n}\n\n// Preset gradients for demo purposes\nexport const gradientPresets = {\n sunset: \"linear-gradient(135deg, #667eea 0%, #764ba2 100%)\",\n ocean: \"linear-gradient(135deg, #667eea 0%, #4ca1af 100%)\",\n fire: \"linear-gradient(135deg, #f093fb 0%, #f5576c 100%)\",\n forest: \"linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)\",\n galaxy: \"linear-gradient(135deg, #7303c0 0%, #ec38bc 50%, #fdeff9 100%)\",\n aurora: \"linear-gradient(135deg, #89f7fe 0%, #66a6ff 100%)\",\n peach: \"linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%)\",\n lavender: \"linear-gradient(135deg, #a8edea 0%, #fed6e3 100%)\",\n}\n"
|
|
}
|
|
],
|
|
"type": "components:animation"
|
|
} |