feat(ui): Midnight Glass v2 전면 리디자인 — 기반+셸+대시보드

- theme.ts 재작성: glass/gradient/glow 토큰군, 기본 dark=Midnight(블루),
  6종 테마 변형 전부 파생 토큰 유지 (테마 시스템 보존)
- DS 리스킨(API 호환): 글래스 카드/버튼, Pretendard 타이포, 소프트 LED
- 신규: GradientWave(canvas 스펙트럼 음파), StatRing(컬러 링 타일)
- 보더리스: frame:false + 커스텀 TitleBar, 네이티브 스크롤바 제거(폭 불변)
  + OverlayScrollbars 오버레이 스크롤
- AppLayout 와이드 사이드바(lucide, 그라디언트 액티브 필), 대시보드 재구축,
  StatusBar(브랜드/로컬시간), Pretendard 번들, i18n +20키(ko/en)
This commit is contained in:
Yun Chan 2026-07-21 20:14:15 +09:00
parent f3ca0eafd4
commit a8a1d6e546
25 changed files with 1638 additions and 960 deletions

View file

@ -0,0 +1,97 @@
// 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: '8px',
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>
)
}