11import { Tabs , TabsContent } from "@radix-ui/react-tabs" ;
22import { useNavigate , useRouter , useSearch } from "@tanstack/react-router" ;
3- import { DatabaseZapIcon , XIcon } from "lucide-react" ;
3+ import { CheckIcon , DatabaseZapIcon , LinkIcon , XIcon } from "lucide-react" ;
44import { AnimatePresence , motion } from "motion/react" ;
55import {
66 useCallback ,
@@ -22,6 +22,60 @@ import { ScrollArea } from "../ui/scroll-area";
2222import { ScrollShadow } from "../ui/scroll-shadow" ;
2323import { TutorialTableOfContents } from "./TutorialTableOfContents" ;
2424
25+ function CopyArticleLinkButton ( { activeStep } : { activeStep : string | null } ) {
26+ const [ copied , setCopied ] = useState ( false ) ;
27+
28+ const copyLink = useCallback ( ( ) => {
29+ if ( ! activeStep ) return ;
30+
31+ const url = new URL ( window . location . href ) ;
32+ url . searchParams . set (
33+ "article" ,
34+ encodeURIComponent ( activeStep . toLowerCase ( ) ) ,
35+ ) ;
36+
37+ navigator . clipboard . writeText ( url . toString ( ) ) . then ( ( ) => {
38+ setCopied ( true ) ;
39+ setTimeout ( ( ) => setCopied ( false ) , 2000 ) ;
40+ } ) ;
41+ } , [ activeStep ] ) ;
42+
43+ return (
44+ < Button
45+ variant = "outline"
46+ size = "sm"
47+ onClick = { copyLink }
48+ className = "absolute top-2 right-4 z-20"
49+ title = "Copy link to this article"
50+ >
51+ < AnimatePresence mode = "wait" initial = { false } >
52+ { copied ? (
53+ < motion . div
54+ key = "check"
55+ initial = { { scale : 0.5 , opacity : 0 } }
56+ animate = { { scale : 1 , opacity : 1 } }
57+ exit = { { scale : 0.5 , opacity : 0 } }
58+ transition = { { duration : 0.15 } }
59+ >
60+ < CheckIcon className = "h-4 w-4 text-green-600" />
61+ </ motion . div >
62+ ) : (
63+ < motion . div
64+ key = "link"
65+ initial = { { scale : 0.5 , opacity : 0 } }
66+ animate = { { scale : 1 , opacity : 1 } }
67+ exit = { { scale : 0.5 , opacity : 0 } }
68+ transition = { { duration : 0.15 } }
69+ >
70+ < LinkIcon className = "h-4 w-4" />
71+ </ motion . div >
72+ ) }
73+ </ AnimatePresence >
74+ { copied ? "Copied!" : "Copy link" }
75+ </ Button >
76+ ) ;
77+ }
78+
2579function FloatingWindowHeader ( { toggleWindow } : { toggleWindow : ( ) => void } ) {
2680 return (
2781 < div className = "text-sm bg-linear-to-r bg-primary text-primary-foreground border-b border-border dark:border-b-primary px-2 py-1 flex items-center" >
@@ -365,6 +419,7 @@ function FloatingWindow({
365419 isResizing && "pointer-events-none" ,
366420 ) }
367421 >
422+ < CopyArticleLinkButton activeStep = { activeStep } />
368423 < ScrollShadow
369424 position = "top"
370425 visible = { canScrollUp }
0 commit comments