Layout.tsx 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. 'use client';
  2. import { useState, useCallback } from 'react';
  3. import Styles from '../styles/layout.module.scss';
  4. import Header from './Header';
  5. import Footer from './Footer'
  6. import ChannelSidebar from '@/app/component/channel/ChannelSidebar';
  7. import MarketSidebar from '@/app/component/MarketSidebar';
  8. import { ChannelListItem } from '@/types/channel';
  9. import { FEATURE_CHANNEL } from '@/constants/features';
  10. type Props = {
  11. children: React.ReactNode;
  12. initialChannels?: ChannelListItem[];
  13. // 우측 유틸 슬롯 (D6 3열 그리드) — 제공 페이지만 desktop 이상에서 3열 렌더
  14. utility?: React.ReactNode;
  15. };
  16. export default function Layout({ children, initialChannels, utility }: Props) {
  17. const [sidebarOpen, setSidebarOpen] = useState(false);
  18. const toggleSidebar = useCallback(() => {
  19. setSidebarOpen((prev) => !prev);
  20. }, []);
  21. const closeOverlay = useCallback(() => {
  22. setSidebarOpen(false);
  23. }, []);
  24. return (
  25. <>
  26. {/* 접근성: 스킵 링크 — Tab 첫 포커스 시 노출 */}
  27. <a href='#main' className={Styles.skipLink}>본문 바로가기</a>
  28. <div id='container' className={`${Styles.container}${sidebarOpen ? ` ${Styles.sidebarOpen}` : ''}${utility ? ` ${Styles.hasUtility}` : ''}`}>
  29. <Header sidebarOpen={sidebarOpen} onToggle={toggleSidebar} />
  30. {/* 모바일 오버레이 */}
  31. <div className={Styles.overlay} onClick={closeOverlay} />
  32. {/* 좌측 사이드바 — 채널 기능 ON: 채널 목록 / OFF: 관심종목 자리표시 (MarketSidebar) */}
  33. <aside id='aside' className={Styles.aside} aria-label='사이드바'>
  34. {FEATURE_CHANNEL ? (
  35. <ChannelSidebar initialChannels={initialChannels} />
  36. ) : (
  37. <MarketSidebar />
  38. )}
  39. </aside>
  40. {/* 메인 내용 */}
  41. <main id='main' className={`${Styles.main} relative`}>
  42. {children}
  43. </main>
  44. {/* 우측 유틸 슬롯 (desktop 이상 전용) */}
  45. {utility && (
  46. <aside id='utility' className={Styles.utility} aria-label='보조 콘텐츠'>
  47. {utility}
  48. </aside>
  49. )}
  50. {/* 하단 */}
  51. <Footer />
  52. </div>
  53. </>
  54. );
  55. }