Phase 8: SSOT 리팩토링 + HotkeyRecordModal + Dashboard 재작성

- d3roPalette 11개 토큰 추가 (sidebar, chassis, inactive 등)
- DS 컴포넌트 6개 + 페이지 5개 매직넘버 → 팔레트 참조 (0개 잔여)
- HotkeyRecordModal 신규: 커스텀 핫키 녹화 모달
- SettingsModal 재작성: 음성 모드 3개(받아쓰기/Agent/원터치) + 핫키 변경
- DashboardPage 재작성: Hero + 통계 4카드 + CRT 서비스 상태 + 히스토리 날짜 그룹핑
- recording-tip 색상 수정: #1F5DF2(파란) → #f25b29(앰버)
- 설계 문서: phase-8.md, speakly-settings-ui.md
This commit is contained in:
Yun Chan 2026-04-05 09:54:55 +09:00
parent f41fc277e3
commit 28371a9d1a
19 changed files with 1879 additions and 403 deletions

View file

@ -7,10 +7,10 @@ import AddIcon from '@mui/icons-material/Add'
import DeleteIcon from '@mui/icons-material/Delete'
import SearchIcon from '@mui/icons-material/Search'
import { MetalCard, PhosphorText, PhysicalButton } from '../components/ds'
import { d3roPalette, d3roFontMono } from '../theme'
import type { DictionaryEntry, DictionaryPage as DictPageData } from '@shared/types'
const PAGE_SIZE = 50
const MONO = 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace'
export function DictionaryPage(): React.ReactElement {
const [data, setData] = useState<DictPageData | null>(null)
@ -40,7 +40,7 @@ export function DictionaryPage(): React.ReactElement {
return (
<Box sx={{ maxWidth: 900, mx: 'auto', p: 4 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
<PhosphorText variant="label" sx={{ color: '#77797c' }}>
<PhosphorText variant="label" sx={{ color: d3roPalette.text.inactive }}>
CUSTOM DICTIONARY {data?.total ?? 0} WORDS
</PhosphorText>
<Button variant="contained" startIcon={<AddIcon />} onClick={() => setAddOpen(true)} size="small">
@ -53,8 +53,8 @@ export function DictionaryPage(): React.ReactElement {
value={search}
onChange={(e) => setSearch(e.target.value)}
fullWidth
sx={{ mb: 3, '& .MuiInputBase-input': { fontFamily: MONO, fontSize: '12px', letterSpacing: '0.5px' } }}
slotProps={{ input: { startAdornment: <InputAdornment position="start"><SearchIcon sx={{ color: '#77797c', fontSize: 18 }} /></InputAdornment> } }}
sx={{ mb: 3, '& .MuiInputBase-input': { fontFamily: d3roFontMono, fontSize: '12px', letterSpacing: '0.5px' } }}
slotProps={{ input: { startAdornment: <InputAdornment position="start"><SearchIcon sx={{ color: d3roPalette.text.inactive, fontSize: 18 }} /></InputAdornment> } }}
/>
{loading ? (
@ -72,17 +72,17 @@ export function DictionaryPage(): React.ReactElement {
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Box>
<Box sx={{ display: 'flex', alignItems: 'baseline', gap: 1.5 }}>
<Box sx={{ fontSize: '14px', fontWeight: 600, color: '#fff' }}>{entry.word}</Box>
<Box sx={{ fontSize: '14px', fontWeight: 600, color: d3roPalette.text.primary }}>{entry.word}</Box>
{entry.pronunciation && (
<Box sx={{ fontFamily: MONO, fontSize: '11px', color: '#5c2615' }}>[{entry.pronunciation}]</Box>
<Box sx={{ fontFamily: d3roFontMono, fontSize: '11px', color: d3roPalette.text.dimLabel }}>[{entry.pronunciation}]</Box>
)}
</Box>
<Box sx={{ fontFamily: MONO, fontSize: '10px', color: '#77797c', mt: 0.5, letterSpacing: '0.5px' }}>
<Box sx={{ fontFamily: d3roFontMono, fontSize: '10px', color: d3roPalette.text.inactive, mt: 0.5, letterSpacing: '0.5px' }}>
{entry.category.toUpperCase()} · {entry.usageCount}× USED
</Box>
</Box>
<IconButton size="small" onClick={() => { window.electronAPI.dictionary.delete({ id: entry.id }); loadData() }}
sx={{ color: '#77797c', '&:hover': { color: '#ef4444' } }}>
sx={{ color: d3roPalette.text.inactive, '&:hover': { color: d3roPalette.tag.red } }}>
<DeleteIcon sx={{ fontSize: 16 }} />
</IconButton>
</Box>