feat(dictation): surface "Talk now, think later" empty state and hide post-transcript actions
Reduces first-run cognitive load. Save / Copy / Clear / Export now appear only once the user has a transcript or is recording, sliding in via animate-fade-in (brand --duration-ui + ease-out-quart). Template and Extract Tasks remain always-visible because they shape transcription rather than acting on completed content. Empty state now leads with the catchphrase as a brand statement (font-display italic 28px) with the hotkey hint demoted to body-font tertiary. EmptyState gains an optional headline prop; callers without it render unchanged.
This commit is contained in:
@@ -1,12 +1,17 @@
|
|||||||
<script>
|
<script>
|
||||||
let { icon: Icon = null, message = '', actionLabel = '', onAction = null } = $props();
|
let { icon: Icon = null, headline = '', message = '', actionLabel = '', onAction = null } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col items-center justify-center gap-4 py-16 text-text-secondary">
|
<div class="flex flex-col items-center justify-center gap-3 py-16 text-text-secondary">
|
||||||
{#if Icon}
|
{#if Icon}
|
||||||
<Icon size={48} strokeWidth={1} class="opacity-40" aria-hidden="true" />
|
<Icon size={48} strokeWidth={1} class="opacity-40 mb-1" aria-hidden="true" />
|
||||||
|
{/if}
|
||||||
|
{#if headline}
|
||||||
|
<p class="font-display italic text-[28px] text-text text-center leading-tight">{headline}</p>
|
||||||
|
{/if}
|
||||||
|
{#if message}
|
||||||
|
<p class="text-center max-w-xs leading-relaxed {headline ? 'text-text-tertiary' : ''}">{message}</p>
|
||||||
{/if}
|
{/if}
|
||||||
<p class="text-center max-w-xs leading-relaxed">{message}</p>
|
|
||||||
{#if actionLabel && onAction}
|
{#if actionLabel && onAction}
|
||||||
<button onclick={onAction}
|
<button onclick={onAction}
|
||||||
class="px-4 py-2 rounded-lg bg-accent text-bg text-sm font-medium hover:bg-accent-hover"
|
class="px-4 py-2 rounded-lg bg-accent text-bg text-sm font-medium hover:bg-accent-hover"
|
||||||
|
|||||||
@@ -705,6 +705,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let taskCount = $derived(tasks.filter((t) => !t.done).length);
|
let taskCount = $derived(tasks.filter((t) => !t.done).length);
|
||||||
|
let hasTranscript = $derived(transcript.trim().length > 0);
|
||||||
|
|
||||||
let wordCount = $derived.by(() => {
|
let wordCount = $derived.by(() => {
|
||||||
const trimmed = transcript.trim();
|
const trimmed = transcript.trim();
|
||||||
@@ -937,37 +938,41 @@
|
|||||||
onclick={manualExtractTasks}
|
onclick={manualExtractTasks}
|
||||||
disabled={aiProcessing || !transcript.trim()}
|
disabled={aiProcessing || !transcript.trim()}
|
||||||
>{aiProcessing ? "Extracting..." : "Extract Tasks"}</button>
|
>{aiProcessing ? "Extracting..." : "Extract Tasks"}</button>
|
||||||
<span class="w-px h-4 bg-border-subtle flex-shrink-0"></span>
|
{#if hasTranscript || page.recording}
|
||||||
<button
|
<div class="flex items-center gap-1 animate-fade-in">
|
||||||
class="btn-md rounded-lg text-text-secondary hover:bg-hover hover:text-text flex-shrink-0 whitespace-nowrap
|
<span class="w-px h-4 bg-border-subtle flex-shrink-0"></span>
|
||||||
{!transcript.trim() ? 'opacity-40 cursor-default' : ''}"
|
<button
|
||||||
onclick={saveTypedText}
|
class="btn-md rounded-lg text-text-secondary hover:bg-hover hover:text-text flex-shrink-0 whitespace-nowrap
|
||||||
disabled={!transcript.trim()}
|
{!transcript.trim() ? 'opacity-40 cursor-default' : ''}"
|
||||||
>Save</button>
|
onclick={saveTypedText}
|
||||||
<button
|
disabled={!transcript.trim()}
|
||||||
class="btn-md rounded-lg text-text-secondary hover:bg-hover hover:text-text flex-shrink-0 whitespace-nowrap"
|
>Save</button>
|
||||||
onclick={copyAll}
|
<button
|
||||||
>Copy</button>
|
class="btn-md rounded-lg text-text-secondary hover:bg-hover hover:text-text flex-shrink-0 whitespace-nowrap"
|
||||||
<button
|
onclick={copyAll}
|
||||||
class="btn-md rounded-lg text-text-secondary hover:bg-hover hover:text-text flex-shrink-0 whitespace-nowrap"
|
>Copy</button>
|
||||||
onclick={clearTranscript}
|
<button
|
||||||
>Clear</button>
|
class="btn-md rounded-lg text-text-secondary hover:bg-hover hover:text-text flex-shrink-0 whitespace-nowrap"
|
||||||
<div class="relative flex-shrink-0">
|
onclick={clearTranscript}
|
||||||
<button
|
>Clear</button>
|
||||||
class="btn-md rounded-lg text-text-secondary hover:bg-hover hover:text-text whitespace-nowrap"
|
<div class="relative flex-shrink-0">
|
||||||
onclick={() => { showExportMenu = !showExportMenu; showTemplateMenu = false; }}
|
<button
|
||||||
>Export</button>
|
class="btn-md rounded-lg text-text-secondary hover:bg-hover hover:text-text whitespace-nowrap"
|
||||||
{#if showExportMenu}
|
onclick={() => { showExportMenu = !showExportMenu; showTemplateMenu = false; }}
|
||||||
<div class="absolute right-0 top-full mt-1 bg-bg-card border border-border rounded-lg shadow-lg py-1 z-10 animate-fade-in min-w-[120px]">
|
>Export</button>
|
||||||
{#each [["txt", "Plain Text"], ["md", "Markdown"], ["csv", "CSV"], ["html", "HTML"], ["srt", "SRT Subtitles"], ["vtt", "WebVTT"]] as [fmt, label]}
|
{#if showExportMenu}
|
||||||
<button
|
<div class="absolute right-0 top-full mt-1 bg-bg-card border border-border rounded-lg shadow-lg py-1 z-10 animate-fade-in min-w-[120px]">
|
||||||
class="w-full text-left btn-md text-text-secondary hover:bg-hover hover:text-text"
|
{#each [["txt", "Plain Text"], ["md", "Markdown"], ["csv", "CSV"], ["html", "HTML"], ["srt", "SRT Subtitles"], ["vtt", "WebVTT"]] as [fmt, label]}
|
||||||
onclick={() => handleExport(fmt)}
|
<button
|
||||||
>{label}</button>
|
class="w-full text-left btn-md text-text-secondary hover:bg-hover hover:text-text"
|
||||||
{/each}
|
onclick={() => handleExport(fmt)}
|
||||||
|
>{label}</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Template indicator -->
|
<!-- Template indicator -->
|
||||||
@@ -1008,7 +1013,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else if !transcript.trim() && !page.recording && !transcribing}
|
{:else if !transcript.trim() && !page.recording && !transcribing}
|
||||||
<div class="flex-1 flex items-center justify-center">
|
<div class="flex-1 flex items-center justify-center">
|
||||||
<EmptyState icon={Mic} message={`Press record or ${settings.globalHotkey}`} />
|
<EmptyState icon={Mic} headline="Talk now, think later." message={`Press record, or ${settings.globalHotkey}.`} />
|
||||||
</div>
|
</div>
|
||||||
<textarea
|
<textarea
|
||||||
bind:this={textareaEl}
|
bind:this={textareaEl}
|
||||||
|
|||||||
Reference in New Issue
Block a user