- accent.amber/amberDim/amberGlow -> accent.main/dim/glow 91건 (34파일). theme.ts의 @deprecated 별칭 참조 제거 (본체 main 사용). - inline fontSize/fontWeight -> d3roTypo, borderRadius -> d3roRadius 토큰 37건. - P4: 화면 고유 수치(스탯 fontSize, 레이아웃 width)는 토큰화 제외. 정책: docs/REFACTOR_POLICY.md DP1, 이식 인사이트 P4
97 lines
2.7 KiB
TypeScript
97 lines
2.7 KiB
TypeScript
// src/renderer/components/TitleBar.tsx
|
|
// v2 보더리스 UI: 커스텀 타이틀바 — 투명 드래그 스트립 + 우측 윈도우 컨트롤.
|
|
// 콘텐츠 위에 오버레이되며(레퍼런스처럼 컨트롤이 콘텐츠 위에 떠 있음),
|
|
// 드래그 영역은 -webkit-app-region: drag, 버튼은 no-drag.
|
|
|
|
import { useState, useEffect, useCallback } from 'react'
|
|
import { Box } from '@mui/material'
|
|
import { Minus, Square, Copy, X } from 'lucide-react'
|
|
import { d3roPalette } from '@d3ro/ui/theme'
|
|
|
|
const BAR_HEIGHT = 40
|
|
|
|
export function TitleBar(): React.ReactElement {
|
|
const [isMaximized, setIsMaximized] = useState(false)
|
|
|
|
useEffect(() => {
|
|
window.electronAPI.window.isMaximized().then((max) => setIsMaximized(max))
|
|
}, [])
|
|
|
|
const handleMaximize = useCallback(() => {
|
|
window.electronAPI.window.maximize()
|
|
// 토글 후 상태 재조회 (이벤트 채널이 없어 폴링 1회)
|
|
setTimeout(() => {
|
|
window.electronAPI.window.isMaximized().then((max) => setIsMaximized(max))
|
|
}, 120)
|
|
}, [])
|
|
|
|
return (
|
|
<Box
|
|
sx={{
|
|
position: 'fixed',
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
height: BAR_HEIGHT,
|
|
zIndex: (theme) => theme.zIndex.drawer + 2,
|
|
display: 'flex',
|
|
justifyContent: 'flex-end',
|
|
alignItems: 'flex-start',
|
|
WebkitAppRegion: 'drag',
|
|
pointerEvents: 'auto',
|
|
}}
|
|
>
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
WebkitAppRegion: 'no-drag',
|
|
m: '6px 8px 0 0',
|
|
gap: '2px',
|
|
}}
|
|
>
|
|
<ControlButton onClick={() => window.electronAPI.window.minimize()}>
|
|
<Minus size={14} />
|
|
</ControlButton>
|
|
<ControlButton onClick={handleMaximize}>
|
|
{isMaximized ? <Copy size={12} style={{ transform: 'scaleX(-1)' }} /> : <Square size={11} />}
|
|
</ControlButton>
|
|
<ControlButton danger onClick={() => window.electronAPI.window.close()}>
|
|
<X size={14} />
|
|
</ControlButton>
|
|
</Box>
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
function ControlButton({
|
|
children,
|
|
danger = false,
|
|
onClick,
|
|
}: {
|
|
children: React.ReactNode
|
|
danger?: boolean
|
|
onClick: () => void
|
|
}): React.ReactElement {
|
|
return (
|
|
<Box
|
|
onClick={onClick}
|
|
sx={{
|
|
width: 34,
|
|
height: 28,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
borderRadius: d3roRadius.small,
|
|
cursor: 'pointer',
|
|
color: d3roPalette.text.inactive,
|
|
transition: 'background-color 0.12s ease, color 0.12s ease',
|
|
'&:hover': {
|
|
bgcolor: danger ? d3roPalette.tag.redBg : d3roPalette.glass.raised,
|
|
color: danger ? d3roPalette.tag.red : d3roPalette.text.primary,
|
|
},
|
|
}}
|
|
>
|
|
{children}
|
|
</Box>
|
|
)
|
|
}
|