feat(music-lab): Phase 2 UI — StemModal, SyncedLyricsPlayer, Style Boost, WAV 변환

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-08 09:05:07 +09:00
parent 7a591bb0f1
commit 0849c70644
5 changed files with 349 additions and 2 deletions

View File

@@ -0,0 +1,55 @@
import React, { useState } from 'react';
const STEM_ICONS = {
vocal: '🎤', backing_vocals: '🎶', drums: '🥁', bass: '🎸',
guitar: '🎸', keyboard: '🎹', strings: '🎻', brass: '🎺',
woodwinds: '🪈', percussion: '🪘', synth: '🎛', fx: '✨',
};
const StemModal = ({ stems, onClose }) => {
const [playingStem, setPlayingStem] = useState(null);
if (!stems || Object.keys(stems).length === 0) return null;
return (
<div className="ms-modal-overlay" onClick={onClose}>
<div className="ms-modal ms-modal--wide" onClick={(e) => e.stopPropagation()}>
<div className="ms-modal__header">
<h3 className="ms-modal__title">12 Stems</h3>
<span className="ms-modal__subtitle"> 스템을 개별 재생 다운로드할 있습니다</span>
<button type="button" className="ms-modal__close" onClick={onClose}></button>
</div>
<div className="ms-stem-grid">
{Object.entries(stems).map(([name, url]) => {
if (!url) return null;
const isPlaying = playingStem === name;
return (
<div key={name} className={`ms-stem-card ${isPlaying ? 'is-playing' : ''}`}>
<span className="ms-stem-card__icon">{STEM_ICONS[name] || '🎵'}</span>
<span className="ms-stem-card__name">{name.replace(/_/g, ' ')}</span>
<div className="ms-stem-card__actions">
<button
type="button"
className="ms-btn--icon"
onClick={() => setPlayingStem(isPlaying ? null : name)}
>
{isPlaying ? '■' : '▶'}
</button>
<a href={url} download className="ms-btn--icon" aria-label="다운로드"></a>
</div>
{isPlaying && (
<audio src={url} autoPlay onEnded={() => setPlayingStem(null)} />
)}
</div>
);
})}
</div>
<div className="ms-modal__actions">
<button type="button" className="ms-btn ms-btn--ghost" onClick={onClose}>닫기</button>
</div>
</div>
</div>
);
};
export default StemModal;