|
| 1 | +/** |
| 2 | + * ProjectContentWrapper Component |
| 3 | + * |
| 4 | + * Manages visibility of terminal and content panels based on current route. |
| 5 | + * Uses CSS-based visibility toggling to preserve component state during navigation. |
| 6 | + * |
| 7 | + * Architecture: |
| 8 | + * - Terminal panel: Persisted in layout, never unmounts (preserves WebSocket state) |
| 9 | + * - Content panel: Regular page content (overview, settings, etc.) |
| 10 | + * - Visibility controlled via data attributes + CSS instead of conditional rendering |
| 11 | + * |
| 12 | + * Why this approach: |
| 13 | + * - Leverages Next.js 16 Layout Persistence feature |
| 14 | + * - Avoids component unmount/remount overhead |
| 15 | + * - Maintains WebSocket connections and terminal state |
| 16 | + * - Better performance than Parallel Routes (no redundant RSC fetches) |
| 17 | + */ |
| 18 | + |
1 | 19 | 'use client'; |
2 | 20 |
|
3 | 21 | import { usePathname } from 'next/navigation'; |
4 | 22 |
|
5 | 23 | import { TerminalContainer, type TerminalContainerProps } from '@/components/terminal/terminal-container'; |
6 | 24 |
|
| 25 | +import styles from './project-content-wrapper.module.css'; |
| 26 | + |
| 27 | +// ============================================================================ |
| 28 | +// Types |
| 29 | +// ============================================================================ |
| 30 | + |
7 | 31 | interface ProjectContentWrapperProps extends TerminalContainerProps { |
8 | 32 | children: React.ReactNode; |
9 | 33 | } |
10 | 34 |
|
| 35 | +// ============================================================================ |
| 36 | +// Component |
| 37 | +// ============================================================================ |
| 38 | + |
11 | 39 | export function ProjectContentWrapper({ |
12 | 40 | children, |
13 | 41 | project, |
14 | 42 | sandbox, |
15 | 43 | }: ProjectContentWrapperProps) { |
16 | 44 | const pathname = usePathname(); |
17 | | - // Check if the current path ends with /terminal |
18 | | - const isTerminalPage = pathname?.endsWith('/terminal'); |
| 45 | + |
| 46 | + // Determine which panel to display based on current route |
| 47 | + const isTerminalPage = pathname?.endsWith('/terminal') ?? false; |
19 | 48 |
|
20 | 49 | return ( |
21 | | - <> |
22 | | - <div |
23 | | - className="w-full h-full" |
24 | | - style={{ display: isTerminalPage ? 'block' : 'none' }} |
| 50 | + <div className={styles.wrapper}> |
| 51 | + {/* Terminal Panel - Persisted across navigation */} |
| 52 | + <div |
| 53 | + data-visible={isTerminalPage} |
| 54 | + className={`${styles.panel} ${styles.terminalPanel}`} |
| 55 | + aria-hidden={!isTerminalPage} |
| 56 | + aria-live={isTerminalPage ? 'polite' : undefined} |
| 57 | + role="region" |
| 58 | + aria-label="Terminal Console" |
25 | 59 | > |
26 | | - <TerminalContainer |
27 | | - project={project} |
28 | | - sandbox={sandbox} |
| 60 | + <TerminalContainer |
| 61 | + project={project} |
| 62 | + sandbox={sandbox} |
29 | 63 | isVisible={isTerminalPage} |
30 | 64 | /> |
31 | 65 | </div> |
32 | | - <div |
33 | | - className="w-full h-full flex flex-col overflow-hidden min-h-0" |
34 | | - style={{ display: !isTerminalPage ? 'flex' : 'none' }} |
| 66 | + |
| 67 | + {/* Content Panel - Regular pages (overview, settings, env, etc.) */} |
| 68 | + <div |
| 69 | + data-visible={!isTerminalPage} |
| 70 | + className={`${styles.panel} ${styles.contentPanel}`} |
| 71 | + aria-hidden={isTerminalPage} |
| 72 | + role="main" |
35 | 73 | > |
36 | 74 | {children} |
37 | 75 | </div> |
38 | | - </> |
| 76 | + </div> |
39 | 77 | ); |
40 | 78 | } |
0 commit comments