Files
goldenChart/frontend/src/components/AppPopup.tsx
T
Macbook eaf067db1c Unify popup UI to TradeAlertModal design system.
Add AppPopup shell, shared CSS, MUI theme overrides, and center positioning for consistent modal styling across frontend and frontend_golden.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 16:56:37 +09:00

146 lines
3.8 KiB
TypeScript

/**
* AppPopup — TradeAlertModal(tam-) 기준 공통 팝업 셸
* 신규 팝업은 이 컴포넌트를 사용하세요.
*/
import React from 'react';
import { createPortal } from 'react-dom';
import { useDraggablePanel } from '../hooks/useDraggablePanel';
export const APP_POPUP_ACCENT = '#3f7ef5';
export const APP_POPUP_ACCENT_GLOW = 'rgba(63,126,245,0.22)';
export interface AppPopupProps {
onClose: () => void;
title: React.ReactNode;
titleKo?: React.ReactNode;
badge?: string;
accentColor?: string;
children: React.ReactNode;
width?: number | string;
maxWidth?: number | string;
className?: string;
shellClassName?: string;
overlayClassName?: string;
bodyClassName?: string;
centered?: boolean;
zIndex?: number;
closeOnBackdrop?: boolean;
backdrop?: boolean;
portal?: boolean;
headerExtra?: React.ReactNode;
footer?: React.ReactNode;
initialPosition?: { x: number; y: number };
}
export const AppPopup: React.FC<AppPopupProps> = ({
onClose,
title,
titleKo,
badge,
accentColor = APP_POPUP_ACCENT,
children,
width = 400,
maxWidth = '96vw',
className,
shellClassName,
overlayClassName,
bodyClassName = 'app-popup-body',
centered = true,
zIndex = 9999,
closeOnBackdrop = true,
backdrop = false,
portal = true,
headerExtra,
footer,
initialPosition,
}) => {
const accentGlow = accentColor === APP_POPUP_ACCENT
? APP_POPUP_ACCENT_GLOW
: `${accentColor}38`;
const {
panelRef,
dragging,
onHeaderPointerDown,
headerTouchStyle,
panelStyle,
headerCursor,
} = useDraggablePanel({ centerOnMount: centered, initialPosition });
const overlayCls = [
'app-popup-overlay',
backdrop ? 'app-popup-overlay--dim' : '',
overlayClassName ?? '',
className ?? '',
].filter(Boolean).join(' ');
const shellCls = ['app-popup-shell', shellClassName ?? ''].filter(Boolean).join(' ');
const content = (
<div
className={overlayCls}
style={{ zIndex }}
onMouseDown={e => {
if (closeOnBackdrop && backdrop && e.target === e.currentTarget) onClose();
}}
>
<div
ref={panelRef}
className={shellCls}
style={{
...panelStyle,
width,
maxWidth,
cursor: dragging ? 'grabbing' : undefined,
'--app-popup-accent': accentColor,
'--app-popup-accent-glow': accentGlow,
'--tam-accent': accentColor,
'--tam-accent-glow': accentGlow,
'--lsp-accent': accentColor,
'--lsp-accent-glow': accentGlow,
} as React.CSSProperties}
onMouseDown={e => e.stopPropagation()}
>
<div
className="gc-popup-header app-popup-header"
onPointerDown={onHeaderPointerDown}
style={{ cursor: headerCursor, ...headerTouchStyle }}
>
<div className="app-popup-header-left">
{badge ? (
<span className="app-popup-badge" style={{ background: accentColor }}>
{badge}
</span>
) : null}
<span className="app-popup-header-title">
{title}
{titleKo != null && titleKo !== '' ? (
<span className="app-popup-header-ko"> ({titleKo})</span>
) : null}
</span>
</div>
{headerExtra}
<button
type="button"
className="app-popup-close gc-popup-close"
onMouseDown={e => e.stopPropagation()}
onClick={onClose}
aria-label="닫기"
>
</button>
</div>
<div className={bodyClassName}>{children}</div>
{footer}
</div>
</div>
);
if (portal && typeof document !== 'undefined' && document.body) {
return createPortal(content, document.body);
}
return content;
};
export default AppPopup;