CryptoPageContent.tsx 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. 'use client';
  2. import { useEffect, useState, useCallback } from 'react';
  3. import { createPortal } from 'react-dom';
  4. import { CryptoProvider, useCryptoContext } from '@/contexts/cryptoProvider';
  5. import CryptoDashboard from './CryptoDashboard';
  6. import CryptoSidebar from './CryptoSidebar';
  7. import MobileCoinList from './MobileCoinList';
  8. import PopupModal from '@/app/component/PopupModal';
  9. import Styles from '@/app/styles/common.module.scss';
  10. import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
  11. import { faChevronLeft } from '@fortawesome/free-solid-svg-icons';
  12. import type { TickerRestData } from '@/types/crypto';
  13. import './mobile-coin-list.scss';
  14. type Props = {
  15. initialTickers: TickerRestData[];
  16. };
  17. function MobileBackButton({ onBack }: { onBack: () => void }) {
  18. const { selectedMarket, tickerMeta } = useCryptoContext();
  19. const meta = tickerMeta.get(selectedMarket);
  20. const displayName = meta?.korName ?? selectedMarket.split('-')[1] ?? selectedMarket;
  21. return (
  22. <div className='mobile-back-bar'>
  23. <button type='button' onClick={onBack}>
  24. <FontAwesomeIcon icon={faChevronLeft} />
  25. {displayName}
  26. </button>
  27. </div>
  28. );
  29. }
  30. export default function CryptoPageContent({ initialTickers }: Props) {
  31. const [containerEl, setContainerEl] = useState<HTMLElement | null>(null);
  32. const [isMobile, setIsMobile] = useState(false);
  33. const [mobileView, setMobileView] = useState<'list' | 'detail'>('list');
  34. useEffect(() => {
  35. setContainerEl(document.getElementById('container'));
  36. return () => setContainerEl(null);
  37. }, []);
  38. useEffect(() => {
  39. const mq = window.matchMedia('(max-width: 1125px)');
  40. setIsMobile(mq.matches);
  41. const handler = (e: MediaQueryListEvent) => {
  42. setIsMobile(e.matches);
  43. if (!e.matches) setMobileView('list');
  44. };
  45. mq.addEventListener('change', handler);
  46. return () => mq.removeEventListener('change', handler);
  47. }, []);
  48. // 하단 탭바 "코인" 클릭 시 목록으로 복귀
  49. useEffect(() => {
  50. const handler = () => setMobileView('list');
  51. window.addEventListener('crypto:showList', handler);
  52. return () => window.removeEventListener('crypto:showList', handler);
  53. }, []);
  54. const handleSelectCoin = useCallback(() => {
  55. setMobileView('detail');
  56. }, []);
  57. const handleBack = useCallback(() => {
  58. setMobileView('list');
  59. }, []);
  60. return (
  61. <CryptoProvider>
  62. <PopupModal position='main' />
  63. {isMobile ? (
  64. mobileView === 'list' ? (
  65. <MobileCoinList
  66. initialTickers={initialTickers}
  67. onSelectCoin={handleSelectCoin}
  68. />
  69. ) : (
  70. <>
  71. <MobileBackButton onBack={handleBack} />
  72. <CryptoDashboard />
  73. </>
  74. )
  75. ) : (
  76. <CryptoDashboard />
  77. )}
  78. {!isMobile && containerEl && createPortal(
  79. <aside id='aside' className={Styles.aside}>
  80. <CryptoSidebar initialTickers={initialTickers} />
  81. </aside>,
  82. containerEl
  83. )}
  84. </CryptoProvider>
  85. );
  86. }