feat: move language card into top row
This commit is contained in:
parent
7ac7c4b216
commit
ea353c963c
@ -67,6 +67,15 @@ describe('UploadScreen', () => {
|
||||
expect(screen.getByRole('heading', { name: 'Language & Dubbing' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders upload, mode, and language cards as first-row regions', () => {
|
||||
renderUploadScreen();
|
||||
|
||||
expect(screen.getByTestId('upload-dropzone-card')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('mode-card')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('language-card')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('subtitle-defaults-card')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows all supported tts languages in a compact always-visible grid', () => {
|
||||
renderUploadScreen();
|
||||
|
||||
@ -84,6 +93,13 @@ describe('UploadScreen', () => {
|
||||
expect(screen.queryByText('DEF')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('keeps the language card controls intact after moving into the first row', () => {
|
||||
renderUploadScreen();
|
||||
|
||||
expect(screen.getByTestId('language-card')).toContainElement(screen.getByTestId('tts-language-grid'));
|
||||
expect(screen.getByRole('button', { name: 'Generate Translated Video' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows a fixed English subtitle language and the supported TTS languages', () => {
|
||||
renderUploadScreen();
|
||||
|
||||
|
||||
@ -34,7 +34,6 @@ export default function UploadScreen({
|
||||
const [subtitleDefaults, setSubtitleDefaults] = useState<SubtitleDefaults>(DEFAULT_SUBTITLE_DEFAULTS);
|
||||
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const activeModeLabel = mode === 'editing' ? m.upload.editingMode : m.upload.simpleMode;
|
||||
const activeModeDescription = mode === 'editing' ? m.upload.editingModeDesc : m.upload.simpleModeDesc;
|
||||
const settingsCardClass =
|
||||
'app-surface rounded-[24px] border border-white/70 px-4 py-4 shadow-[0_18px_60px_-34px_rgba(15,23,42,0.3)] sm:px-5';
|
||||
|
||||
@ -61,13 +60,13 @@ export default function UploadScreen({
|
||||
return (
|
||||
<div
|
||||
data-testid="upload-workbench"
|
||||
className="grid items-start gap-4 lg:gap-5 xl:grid-cols-[minmax(0,1.45fr)_minmax(300px,380px)]"
|
||||
className="grid items-start gap-4 lg:gap-5 xl:grid-cols-[minmax(0,1.55fr)_minmax(240px,0.72fr)_minmax(300px,0.95fr)]"
|
||||
>
|
||||
<section
|
||||
data-testid="upload-dropzone-card"
|
||||
className="app-surface min-w-0 rounded-[28px] border border-white/70 px-4 py-4 shadow-[0_24px_80px_-40px_rgba(15,23,42,0.35)] sm:px-5 sm:py-5 lg:px-6 lg:py-5"
|
||||
className="app-surface min-w-0 rounded-[28px] border border-white/70 px-4 py-4 shadow-[0_24px_80px_-40px_rgba(15,23,42,0.35)] sm:px-5 sm:py-5 lg:px-6 lg:py-5 xl:row-span-2"
|
||||
>
|
||||
<div className="mb-4 flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
|
||||
<div className="mb-4 flex flex-col gap-3">
|
||||
<div className="min-w-0 space-y-2">
|
||||
<h2 className="text-xl font-semibold tracking-tight text-slate-950 sm:text-2xl">
|
||||
{m.upload.uploadPanelTitle}
|
||||
@ -76,14 +75,6 @@ export default function UploadScreen({
|
||||
{m.upload.uploadPanelDescription}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-2xl border border-sky-100 bg-sky-50/80 px-3.5 py-2.5 text-left shadow-sm shadow-sky-100/50">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.2em] text-sky-600">
|
||||
{m.upload.modeWorkflowTitle}
|
||||
</p>
|
||||
<p className="mt-2 text-sm font-semibold text-slate-900">{activeModeLabel}</p>
|
||||
<p className="mt-1 max-w-xs text-xs leading-5 text-slate-600">{activeModeDescription}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative overflow-hidden rounded-[24px] border border-slate-200/80 bg-[linear-gradient(180deg,rgba(248,250,252,0.95)_0%,rgba(226,232,240,0.88)_100%)] p-2.5 sm:p-3">
|
||||
@ -122,17 +113,20 @@ export default function UploadScreen({
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<aside
|
||||
<div
|
||||
data-testid="upload-settings-column"
|
||||
className="min-w-0 space-y-4"
|
||||
className="min-w-0 grid gap-4 xl:contents"
|
||||
>
|
||||
<section className={settingsCardClass}>
|
||||
<section
|
||||
data-testid="mode-card"
|
||||
className={`${settingsCardClass} xl:col-start-2 xl:row-start-1`}
|
||||
>
|
||||
<div className="mb-3">
|
||||
<h2 className="text-base font-semibold text-slate-950">{m.upload.modeWorkflowTitle}</h2>
|
||||
<p className="mt-1 text-sm leading-5 text-slate-600">{m.upload.modeWorkflowDescription}</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2 sm:grid-cols-2">
|
||||
<div className="grid gap-2">
|
||||
<button
|
||||
type="button"
|
||||
aria-pressed={mode === 'editing'}
|
||||
@ -177,7 +171,69 @@ export default function UploadScreen({
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className={settingsCardClass}>
|
||||
<section
|
||||
data-testid="language-card"
|
||||
className={`${settingsCardClass} flex flex-col xl:col-start-3 xl:row-start-1`}
|
||||
>
|
||||
<div className="mb-3">
|
||||
<h2 className="text-base font-semibold text-slate-950">{m.upload.languageDubbingTitle}</h2>
|
||||
<p className="mt-1 text-sm leading-5 text-slate-600">{m.upload.languageDubbingDescription}</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-3 rounded-2xl border border-slate-200 bg-white/80 p-3">
|
||||
<h3 className="font-semibold text-slate-900">{m.upload.subtitleLanguage}</h3>
|
||||
<p className="mt-1 text-xs text-slate-500">{m.upload.subtitleLanguageHint}</p>
|
||||
<div className="mt-3 inline-flex items-center rounded-xl border border-slate-200 bg-slate-50 px-3 py-2 text-sm font-medium text-slate-700">
|
||||
{m.upload.languages.en}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<h3 className="font-semibold text-slate-900">{m.upload.ttsLanguage}</h3>
|
||||
<p className="mt-1 text-xs text-slate-500">{m.upload.ttsLanguageHint}</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
data-testid="tts-language-grid"
|
||||
className="grid grid-cols-2 gap-2"
|
||||
>
|
||||
{LANGUAGES.map((lang) => (
|
||||
<button
|
||||
key={lang.code}
|
||||
type="button"
|
||||
className={`rounded-2xl border px-3 py-2 text-left text-sm font-medium transition-colors ${
|
||||
selectedTtsLanguage === lang.code
|
||||
? 'border-emerald-500 bg-emerald-50 text-emerald-700'
|
||||
: lang.code === 'zh' || lang.code === 'yue'
|
||||
? 'border-orange-100 bg-orange-50 text-orange-600 hover:border-orange-200'
|
||||
: 'border-slate-200 bg-white/80 text-slate-700 hover:border-sky-200 hover:text-sky-700'
|
||||
}`}
|
||||
onClick={() => setSelectedTtsLanguage(lang.code)}
|
||||
>
|
||||
{m.upload.languages[lang.code as keyof typeof m.upload.languages]}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button
|
||||
className={`mt-4 w-full rounded-2xl px-4 py-3 text-sm font-semibold transition-colors ${
|
||||
tempFile
|
||||
? 'bg-[#16a34a] text-white shadow-lg shadow-emerald-200 hover:bg-[#15803d]'
|
||||
: 'cursor-not-allowed bg-slate-200 text-slate-400'
|
||||
}`}
|
||||
onClick={() => {
|
||||
if (tempFile) setShowTrimModal(true);
|
||||
}}
|
||||
disabled={!tempFile}
|
||||
>
|
||||
{m.upload.generateTranslatedVideo}
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<section
|
||||
data-testid="subtitle-defaults-card"
|
||||
className={`${settingsCardClass} xl:col-start-2 xl:col-span-2 xl:row-start-2`}
|
||||
>
|
||||
<div className="mb-3 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h2 className="text-base font-semibold text-slate-950">{m.upload.subtitleDefaults}</h2>
|
||||
@ -265,63 +321,7 @@ export default function UploadScreen({
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className={`${settingsCardClass} flex flex-col`}>
|
||||
<div className="mb-3">
|
||||
<h2 className="text-base font-semibold text-slate-950">{m.upload.languageDubbingTitle}</h2>
|
||||
<p className="mt-1 text-sm leading-5 text-slate-600">{m.upload.languageDubbingDescription}</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-3 rounded-2xl border border-slate-200 bg-white/80 p-3">
|
||||
<h3 className="font-semibold text-slate-900">{m.upload.subtitleLanguage}</h3>
|
||||
<p className="mt-1 text-xs text-slate-500">{m.upload.subtitleLanguageHint}</p>
|
||||
<div className="mt-3 inline-flex items-center rounded-xl border border-slate-200 bg-slate-50 px-3 py-2 text-sm font-medium text-slate-700">
|
||||
{m.upload.languages.en}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<h3 className="font-semibold text-slate-900">{m.upload.ttsLanguage}</h3>
|
||||
<p className="mt-1 text-xs text-slate-500">{m.upload.ttsLanguageHint}</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
data-testid="tts-language-grid"
|
||||
className="grid grid-cols-2 gap-2"
|
||||
>
|
||||
{LANGUAGES.map((lang) => (
|
||||
<button
|
||||
key={lang.code}
|
||||
type="button"
|
||||
className={`rounded-2xl border px-3 py-2 text-left text-sm font-medium transition-colors ${
|
||||
selectedTtsLanguage === lang.code
|
||||
? 'border-emerald-500 bg-emerald-50 text-emerald-700'
|
||||
: lang.code === 'zh' || lang.code === 'yue'
|
||||
? 'border-orange-100 bg-orange-50 text-orange-600 hover:border-orange-200'
|
||||
: 'border-slate-200 bg-white/80 text-slate-700 hover:border-sky-200 hover:text-sky-700'
|
||||
}`}
|
||||
onClick={() => setSelectedTtsLanguage(lang.code)}
|
||||
>
|
||||
{m.upload.languages[lang.code as keyof typeof m.upload.languages]}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button
|
||||
className={`mt-4 w-full rounded-2xl px-4 py-3 text-sm font-semibold transition-colors ${
|
||||
tempFile
|
||||
? 'bg-[#16a34a] text-white shadow-lg shadow-emerald-200 hover:bg-[#15803d]'
|
||||
: 'cursor-not-allowed bg-slate-200 text-slate-400'
|
||||
}`}
|
||||
onClick={() => {
|
||||
if (tempFile) setShowTrimModal(true);
|
||||
}}
|
||||
disabled={!tempFile}
|
||||
>
|
||||
{m.upload.generateTranslatedVideo}
|
||||
</button>
|
||||
</section>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
{showTrimModal && tempFile && (
|
||||
<TrimModal
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user