From ab5c01fdb349366564aab3a26871a953af0149a2 Mon Sep 17 00:00:00 2001 From: shivani11jadhav Date: Sat, 30 May 2026 18:34:58 +0530 Subject: [PATCH] feat: add native web share api button component for roadmap progress #347 --- src/components/home/LearningPaths.tsx | 13 ++- src/components/ui/ShareButton.tsx | 149 ++++++++++++++++++++++++++ 2 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 src/components/ui/ShareButton.tsx diff --git a/src/components/home/LearningPaths.tsx b/src/components/home/LearningPaths.tsx index 3ab3e92..a7437cc 100644 --- a/src/components/home/LearningPaths.tsx +++ b/src/components/home/LearningPaths.tsx @@ -3,6 +3,7 @@ import { useState } from 'react'; import { Clock, BookOpen, ArrowRight, Bell } from 'lucide-react'; import Button from '../ui/Button'; +import ShareButton from '../ui/ShareButton'; import ComingSoonBadge from '../features/ComingSoonBadge'; import styles from './LearningPaths.module.css'; @@ -103,7 +104,17 @@ export default function LearningPaths() { {path.status === 'coming-soon' && }
- {path.difficulty} +
+ {path.difficulty} + +

{path.title}

diff --git a/src/components/ui/ShareButton.tsx b/src/components/ui/ShareButton.tsx new file mode 100644 index 0000000..b6f6c2b --- /dev/null +++ b/src/components/ui/ShareButton.tsx @@ -0,0 +1,149 @@ +"use client"; + +import React, { useState, useEffect } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { Share2, Check } from 'lucide-react'; +import styles from './Button.module.css'; + +interface ShareButtonProps { + url?: string; + title?: string; + text?: string; + className?: string; + variant?: 'primary' | 'secondary' | 'ghost'; + label?: string; + showLabel?: boolean; +} + +export default function ShareButton({ + url, + title, + text, + className = '', + variant = 'secondary', + label = 'Share', + showLabel = true, +}: ShareButtonProps) { + const [copied, setCopied] = useState(false); + const [isMobileShareSupported, setIsMobileShareSupported] = useState(false); + + useEffect(() => { + // Detect native mobile/browser share support safely on mount + if (typeof navigator !== 'undefined' && typeof navigator.share === 'function') { + setIsMobileShareSupported(true); + } + }, []); + + const handleShare = async (e: React.MouseEvent) => { + e.stopPropagation(); // Avoid triggering any card click events + + const shareUrl = url || (typeof window !== 'undefined' ? window.location.href : ''); + const shareTitle = title || (typeof document !== 'undefined' ? document.title : 'DevPath'); + const shareText = text || 'Check out this learning path on DevPath!'; + + if (isMobileShareSupported) { + try { + await navigator.share({ + title: shareTitle, + text: shareText, + url: shareUrl, + }); + } catch (error) { + // If sharing was aborted or failed, ignore it + console.warn('Native share cancelled or failed:', error); + } + } else { + // Desktop fallback: copy to clipboard + try { + await navigator.clipboard.writeText(shareUrl); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (err) { + console.error('Failed to copy to clipboard:', err); + } + } + }; + + return ( +
+ + + + + {copied ? ( + + + + ) : ( + + + + )} + + + {showLabel && ( + + {copied ? 'Copied!' : label} + + )} + + + {/* Hover shine effect */} +
+ + {/* Dynamic background glow on copy */} +
+ + + {/* Custom toast/tooltip popover for desktop users on copy fallback */} + + {copied && !isMobileShareSupported && ( + + + Copied to Clipboard! + + )} + +
+ ); +}