1- import { Box } from "@radix-ui/themes" ;
2- import { useEffect , useRef } from "react" ;
1+ import { ArrowsIn , ArrowsOut , ListChecks , X } from "@phosphor-icons/react" ;
2+ import { Box , Flex , IconButton , Text } from "@radix-ui/themes" ;
3+ import { useEffect , useRef , useState } from "react" ;
4+ import { createPortal } from "react-dom" ;
35import ReactMarkdown from "react-markdown" ;
46import remarkGfm from "remark-gfm" ;
57
@@ -12,6 +14,7 @@ interface PlanContentProps {
1214
1315export function PlanContent ( { id, plan } : PlanContentProps ) {
1416 const scrollRef = useRef < HTMLDivElement > ( null ) ;
17+ const [ isFullscreen , setIsFullscreen ] = useState ( false ) ;
1518
1619 useEffect ( ( ) => {
1720 const el = scrollRef . current ;
@@ -33,14 +36,95 @@ export function PlanContent({ id, plan }: PlanContentProps) {
3336 } ;
3437 } , [ id ] ) ;
3538
39+ useEffect ( ( ) => {
40+ if ( ! isFullscreen ) return ;
41+ const handler = ( e : KeyboardEvent ) => {
42+ if ( e . key === "Escape" ) {
43+ setIsFullscreen ( false ) ;
44+ }
45+ } ;
46+ window . addEventListener ( "keydown" , handler ) ;
47+ return ( ) => window . removeEventListener ( "keydown" , handler ) ;
48+ } , [ isFullscreen ] ) ;
49+
50+ const markdown = (
51+ < ReactMarkdown remarkPlugins = { [ remarkGfm ] } > { plan } </ ReactMarkdown >
52+ ) ;
53+
54+ const portalTarget = document . getElementById ( "mcp-fullscreen-portal" ) ;
55+
56+ if ( isFullscreen && portalTarget ) {
57+ return (
58+ < >
59+ < Flex justify = "end" className = "py-0.5" >
60+ < IconButton
61+ size = "1"
62+ variant = "ghost"
63+ color = "gray"
64+ onClick = { ( ) => setIsFullscreen ( false ) }
65+ title = "Exit fullscreen"
66+ >
67+ < ArrowsIn size = { 12 } />
68+ </ IconButton >
69+ </ Flex >
70+
71+ { createPortal (
72+ < Box
73+ className = "pointer-events-auto absolute inset-0 flex flex-col bg-gray-1"
74+ style = { { transition : "opacity 150ms ease" } }
75+ >
76+ < Flex
77+ align = "center"
78+ justify = "between"
79+ className = "border-gray-6 border-b px-4 py-2"
80+ >
81+ < Flex align = "center" gap = "2" >
82+ < ListChecks size = { 14 } className = "text-gray-11" />
83+ < Text size = "2" className = "text-gray-11" >
84+ Plan
85+ </ Text >
86+ </ Flex >
87+ < IconButton
88+ size = "1"
89+ variant = "ghost"
90+ color = "gray"
91+ onClick = { ( ) => setIsFullscreen ( false ) }
92+ title = "Exit fullscreen (Escape)"
93+ >
94+ < X size = { 14 } />
95+ </ IconButton >
96+ </ Flex >
97+
98+ < Box
99+ ref = { scrollRef }
100+ className = "plan-markdown flex-1 overflow-y-auto p-6"
101+ >
102+ { markdown }
103+ </ Box >
104+ </ Box > ,
105+ portalTarget ,
106+ ) }
107+ </ >
108+ ) ;
109+ }
110+
36111 return (
37112 < Box
38113 ref = { scrollRef }
39- className = "max-h-[50vh] max-w-[750px] overflow-y-auto rounded-lg border-2 border-blue-6 bg-blue-2 p-4"
114+ className = "relative max-h-[50vh] max-w-[750px] overflow-y-auto rounded-lg border-2 border-blue-6 bg-blue-2 p-4"
40115 >
41- < Box className = "plan-markdown text-blue-12" >
42- < ReactMarkdown remarkPlugins = { [ remarkGfm ] } > { plan } </ ReactMarkdown >
43- </ Box >
116+ < IconButton
117+ size = "1"
118+ variant = "ghost"
119+ color = "gray"
120+ className = "sticky top-0 z-10 float-right"
121+ onClick = { ( ) => setIsFullscreen ( true ) }
122+ title = "Expand to fullscreen"
123+ >
124+ < ArrowsOut size = { 12 } />
125+ </ IconButton >
126+
127+ < Box className = "plan-markdown text-blue-12" > { markdown } </ Box >
44128 </ Box >
45129 ) ;
46130}
0 commit comments