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>
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* 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;
|
||||
Reference in New Issue
Block a user