wing-ops/frontend/src/pages/design/float/FloatOverlayContent.tsx

429 lines
16 KiB
TypeScript

// FloatOverlayContent.tsx — Map Overlay + Map Popup 카탈로그
import type { DesignTheme } from '../designTheme';
interface FloatOverlayContentProps {
theme: DesignTheme;
}
const OVERLAY_CASES = [
{
component: 'BacktrackReplayBar',
position: '하단 중앙',
zIndex: 'z-40',
pointerEvents: 'auto',
desc: '역추적 재생 컨트롤 바. 재생/일시정지/슬라이더.',
source: 'common/components/map/BacktrackReplayBar.tsx',
},
{
component: 'MeasureOverlay',
position: '마커 위치',
zIndex: 'z-40',
pointerEvents: 'auto',
desc: '거리 측정 마커 "지우기" 버튼. MapLibre Marker 컴포넌트 활용.',
source: 'common/components/map/MeasureOverlay.tsx',
},
{
component: 'OilDetectionOverlay',
position: 'inset-0 + 우하단 정보',
zIndex: 'z-[15]',
pointerEvents: 'none',
desc: '유류 탐지 결과 마스크 렌더링. OffscreenCanvas 기반. 정보 패널만 클릭 가능.',
source: 'tabs/aerial/components/OilDetectionOverlay.tsx',
},
{
component: 'WeatherMapOverlay',
position: 'absolute inset-0',
zIndex: 'map layer',
pointerEvents: 'none',
desc: '기상 데이터 레이어 오버레이.',
source: 'tabs/weather/components/WeatherMapOverlay.tsx',
},
{
component: 'OceanForecastOverlay',
position: 'absolute inset-0',
zIndex: 'map layer',
pointerEvents: 'none',
desc: '해양 예측 레이어 오버레이.',
source: 'tabs/weather/components/OceanForecastOverlay.tsx',
},
];
export const FloatOverlayContent = ({ theme }: FloatOverlayContentProps) => {
const t = theme;
const isDark = t.mode === 'dark';
const mapMockBg = isDark ? '#0f1a2e' : '#c8d8e8';
const mapGridColor = isDark ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.06)';
return (
<div className="px-8 py-10 flex flex-col gap-12 max-w-[1200px]">
{/* ── 개요 ── */}
<div className="flex flex-col gap-3">
<h2 className="font-sans text-xl font-bold" style={{ color: t.textPrimary }}>
Overlay
</h2>
<p className="font-korean text-sm leading-6" style={{ color: t.textSecondary }}>
<code
className="font-mono text-xs px-1.5 py-0.5 mx-1 rounded"
style={{
backgroundColor: isDark ? 'rgba(76,215,246,0.08)' : 'rgba(6,182,212,0.06)',
color: t.textAccent,
}}
>
position: absolute
</code>
UI. UI를 . Modal과
, .
</p>
</div>
{/* ── Overlay vs Modal 비교 ── */}
<div className="flex flex-col gap-4">
<h3 className="font-sans text-base font-bold" style={{ color: t.textPrimary }}>
Overlay vs Modal
</h3>
<div
className="rounded-lg border border-solid overflow-hidden"
style={{ backgroundColor: t.tableContainerBg, borderColor: t.cardBorder }}
>
<div
className="grid"
style={{
gridTemplateColumns: '160px 1fr 1fr',
backgroundColor: t.tableHeaderBg,
borderBottom: `1px solid ${t.tableRowBorder}`,
}}
>
{['속성', 'Overlay', 'Modal'].map((col) => (
<div key={col} className="py-2.5 px-4">
<span
className="font-mono text-caption uppercase"
style={{ letterSpacing: '1px', color: t.textMuted }}
>
{col}
</span>
</div>
))}
</div>
{[
{ attr: 'position', overlay: 'absolute (지도 기준)', modal: 'fixed (뷰포트 기준)' },
{ attr: '백드롭', overlay: '없음', modal: 'rgba(0,0,0,0.65) + blur' },
{ attr: '클릭 차단', overlay: 'pointer-events: none (일반)', modal: '전체 화면 차단' },
{ attr: 'z-index', overlay: 'z-40 (지도 UI 위)', modal: 'z-[9999] (최상위)' },
{ attr: '크기 기준', overlay: '지도 컨테이너 = 100%', modal: '고정 너비 (380~720px)' },
{
attr: '닫기 방식',
overlay: '기능 비활성화 시 사라짐',
modal: '닫기 버튼 / 백드롭 클릭',
},
].map((row, idx) => (
<div
key={row.attr}
className="grid items-center"
style={{
gridTemplateColumns: '160px 1fr 1fr',
borderTop: idx === 0 ? 'none' : `1px solid ${t.tableRowBorder}`,
}}
>
<div className="py-2.5 px-4">
<span className="font-mono text-caption" style={{ color: t.textMuted }}>
{row.attr}
</span>
</div>
<div className="py-2.5 px-4">
<span className="font-korean text-xs" style={{ color: t.textSecondary }}>
{row.overlay}
</span>
</div>
<div className="py-2.5 px-4">
<span className="font-korean text-xs" style={{ color: t.textSecondary }}>
{row.modal}
</span>
</div>
</div>
))}
</div>
</div>
{/* ── 지도 목업 다이어그램 ── */}
<div className="flex flex-col gap-4">
<h3 className="font-sans text-base font-bold" style={{ color: t.textPrimary }}>
Overlay
</h3>
<div
className="rounded-lg border border-solid p-4 flex flex-col gap-3"
style={{ backgroundColor: t.cardBg, borderColor: t.cardBorder }}
>
<span
className="font-mono text-caption uppercase"
style={{ letterSpacing: '1px', color: t.textMuted }}
>
</span>
{/* 지도 목업 */}
<div
className="relative rounded overflow-hidden"
style={{ backgroundColor: mapMockBg, minHeight: '280px' }}
>
{/* 격자 배경 (지도 모사) */}
<div
className="absolute inset-0"
style={{
backgroundImage: `linear-gradient(${mapGridColor} 1px, transparent 1px), linear-gradient(90deg, ${mapGridColor} 1px, transparent 1px)`,
backgroundSize: '40px 40px',
}}
/>
{/* 지도 레이블 */}
<div className="absolute top-3 left-3">
<span
className="font-mono text-caption"
style={{ color: isDark ? 'rgba(255,255,255,0.25)' : 'rgba(0,0,0,0.25)' }}
>
MapView (position: relative)
</span>
</div>
{/* OilDetectionOverlay — 전체 영역 */}
<div
className="absolute inset-0 pointer-events-none"
style={{ border: `1.5px dashed rgba(6,182,212,0.35)`, borderRadius: '4px' }}
>
<div className="absolute top-10 left-3">
<span
className="font-mono text-caption rounded px-1.5 py-0.5"
style={{ backgroundColor: 'rgba(6,182,212,0.15)', color: t.textAccent }}
>
OilDetectionOverlay inset-0, z-[15], pointer-events:none
</span>
</div>
</div>
{/* MeasureOverlay 마커 */}
<div className="absolute" style={{ top: '80px', left: '120px' }}>
<div
className="rounded-full w-3 h-3 border-2 border-solid"
style={{ backgroundColor: '#ef4444', borderColor: '#ffffff' }}
/>
<div
className="rounded border border-solid px-2 py-0.5 mt-1"
style={{
backgroundColor: isDark ? 'rgba(239,68,68,0.85)' : 'rgba(239,68,68,0.90)',
borderColor: 'transparent',
}}
>
<span className="font-korean text-caption" style={{ color: '#ffffff' }}>
</span>
</div>
<div className="absolute -top-4 left-8">
<span
className="font-mono text-caption rounded px-1 py-0.5"
style={{
backgroundColor: 'rgba(239,68,68,0.15)',
color: '#ef4444',
whiteSpace: 'nowrap',
}}
>
MeasureOverlay Marker
</span>
</div>
</div>
{/* BacktrackReplayBar — 하단 중앙 */}
<div
className="absolute bottom-3 left-1/2 rounded border border-solid px-4 py-2 flex items-center gap-3"
style={{
transform: 'translateX(-50%)',
backgroundColor: isDark ? 'rgba(23,27,40,0.92)' : 'rgba(255,255,255,0.92)',
borderColor: isDark ? 'rgba(66,71,84,0.40)' : '#e2e8f0',
backdropFilter: 'blur(6px)',
}}
>
<span className="font-mono text-caption" style={{ color: t.textAccent }}>
</span>
<span className="font-mono text-caption" style={{ color: t.textAccent }}>
</span>
<div
className="w-24 h-1 rounded"
style={{ backgroundColor: isDark ? 'rgba(66,71,84,0.40)' : '#e2e8f0' }}
>
<div className="h-1 w-10 rounded" style={{ backgroundColor: t.textAccent }} />
</div>
<span className="font-mono text-caption" style={{ color: t.textMuted }}>
</span>
</div>
<div className="absolute bottom-14 left-1/2" style={{ transform: 'translateX(-50%)' }}>
<span
className="font-mono text-caption rounded px-1.5 py-0.5"
style={{
backgroundColor: isDark ? 'rgba(76,215,246,0.12)' : 'rgba(6,182,212,0.10)',
color: t.textAccent,
whiteSpace: 'nowrap',
}}
>
BacktrackReplayBar bottom-3 center, z-40
</span>
</div>
</div>
</div>
</div>
{/* ── Map Popup 서브패턴 ── */}
<div className="flex flex-col gap-4">
<h3 className="font-sans text-base font-bold" style={{ color: t.textPrimary }}>
Map Popup
</h3>
<div
className="rounded-lg border border-solid p-5 flex flex-col gap-4"
style={{ backgroundColor: t.cardBg, borderColor: t.cardBorder }}
>
<p className="font-korean text-sm leading-6" style={{ color: t.textSecondary }}>
<strong>ScatPopup</strong> . Modal(fixed
) , ·
.
</p>
<div className="grid grid-cols-3 gap-3">
{[
{
label: '위치 계산',
value: 'map.project(lngLat)',
desc: '지도 좌표 → 픽셀 좌표 변환',
},
{ label: '위치 업데이트', value: 'map.on("move")', desc: '패닝/줌 시 재계산' },
{ label: 'z-index', value: 'z-[9999]', desc: '다른 오버레이 위' },
].map((item) => (
<div
key={item.label}
className="rounded border border-solid px-3 py-2.5 flex flex-col gap-1"
style={{ borderColor: t.cardBorder }}
>
<span className="font-mono text-caption" style={{ color: t.textMuted }}>
{item.label}
</span>
<span className="font-mono text-xs" style={{ color: t.textAccent }}>
{item.value}
</span>
<span className="font-korean text-caption" style={{ color: t.textSecondary }}>
{item.desc}
</span>
</div>
))}
</div>
<div
className="rounded border border-solid px-3 py-2"
style={{
backgroundColor: isDark ? 'rgba(234,179,8,0.06)' : 'rgba(234,179,8,0.04)',
borderColor: 'rgba(234,179,8,0.25)',
}}
>
<span className="font-korean text-xs" style={{ color: '#eab308' }}>
주의: ScatPopup은 MapLibre GL JS의 Popup/Marker React DOM으로 .
position: absolute로 .
</span>
</div>
<div className="flex items-center gap-2">
<span className="font-mono text-caption" style={{ color: t.textMuted }}>
Source:
</span>
<span className="font-mono text-caption" style={{ color: t.textAccent }}>
tabs/scat/components/ScatPopup.tsx
</span>
</div>
</div>
</div>
{/* ── 사용 사례 목록 ── */}
<div className="flex flex-col gap-4">
<h3 className="font-sans text-base font-bold" style={{ color: t.textPrimary }}>
</h3>
<div
className="rounded-lg border border-solid overflow-hidden"
style={{ backgroundColor: t.tableContainerBg, borderColor: t.cardBorder }}
>
<div
className="grid"
style={{
gridTemplateColumns: '200px 120px 80px 100px 1fr',
backgroundColor: t.tableHeaderBg,
borderBottom: `1px solid ${t.tableRowBorder}`,
}}
>
{['Component', 'Position', 'Z-Index', 'Events', 'Description'].map((col) => (
<div key={col} className="py-2.5 px-3">
<span
className="font-mono text-caption uppercase"
style={{ letterSpacing: '1px', color: t.textMuted }}
>
{col}
</span>
</div>
))}
</div>
{OVERLAY_CASES.map((item, idx) => (
<div
key={item.component}
className="grid items-start"
style={{
gridTemplateColumns: '200px 120px 80px 100px 1fr',
borderTop: idx === 0 ? 'none' : `1px solid ${t.tableRowBorder}`,
}}
>
<div className="py-2.5 px-3">
<span className="font-mono text-xs" style={{ color: t.textPrimary }}>
{item.component}
</span>
</div>
<div className="py-2.5 px-3">
<span className="font-korean text-caption" style={{ color: t.textSecondary }}>
{item.position}
</span>
</div>
<div className="py-2.5 px-3">
<span
className="font-mono text-caption rounded border border-solid px-1.5 py-0.5"
style={{ color: t.textAccent, borderColor: t.cardBorder }}
>
{item.zIndex}
</span>
</div>
<div className="py-2.5 px-3">
<span
className="font-mono text-caption rounded px-1.5 py-0.5"
style={{
color: item.pointerEvents === 'none' ? t.textMuted : '#22c55e',
backgroundColor:
item.pointerEvents === 'none'
? isDark
? 'rgba(140,144,159,0.10)'
: 'rgba(148,163,184,0.10)'
: isDark
? 'rgba(34,197,94,0.10)'
: 'rgba(34,197,94,0.08)',
}}
>
{item.pointerEvents}
</span>
</div>
<div className="py-2.5 px-3">
<span className="font-korean text-xs leading-5" style={{ color: t.textSecondary }}>
{item.desc}
</span>
</div>
</div>
))}
</div>
</div>
</div>
);
};
export default FloatOverlayContent;