agent: components — add WIP task list and visual timer components
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
33
src/lib/components/VisualTimer.svelte
Normal file
33
src/lib/components/VisualTimer.svelte
Normal file
@@ -0,0 +1,33 @@
|
||||
<script>
|
||||
let { totalSeconds = 0, remainingSeconds = 0, size = 48 } = $props();
|
||||
|
||||
let progress = $derived(totalSeconds > 0 ? remainingSeconds / totalSeconds : 0);
|
||||
let radius = $derived((size - 6) / 2);
|
||||
let circumference = $derived(2 * Math.PI * radius);
|
||||
let dashOffset = $derived(circumference * (1 - progress));
|
||||
</script>
|
||||
|
||||
<svg width={size} height={size} class="transform -rotate-90" aria-hidden="true">
|
||||
<!-- Background ring -->
|
||||
<circle
|
||||
cx={size / 2}
|
||||
cy={size / 2}
|
||||
r={radius}
|
||||
fill="none"
|
||||
stroke="var(--color-border-subtle)"
|
||||
stroke-width="3"
|
||||
/>
|
||||
<!-- Progress ring -->
|
||||
<circle
|
||||
cx={size / 2}
|
||||
cy={size / 2}
|
||||
r={radius}
|
||||
fill="none"
|
||||
stroke="var(--color-accent)"
|
||||
stroke-width="3"
|
||||
stroke-linecap="round"
|
||||
stroke-dasharray={circumference}
|
||||
stroke-dashoffset={dashOffset}
|
||||
style="transition: stroke-dashoffset var(--duration-decorative) ease-out"
|
||||
/>
|
||||
</svg>
|
||||
98
src/lib/components/WipTaskList.svelte
Normal file
98
src/lib/components/WipTaskList.svelte
Normal file
@@ -0,0 +1,98 @@
|
||||
<script>
|
||||
import { tasks, addTask, completeTask, uncompleteTask, deleteTask } from '$lib/stores/page.svelte.js';
|
||||
import { ChevronDown } from 'lucide-svelte';
|
||||
|
||||
let { wipLimit = 3 } = $props();
|
||||
|
||||
let newTaskText = $state('');
|
||||
let showOverflow = $state(false);
|
||||
|
||||
let activeTasks = $derived(tasks.filter(t => !t.done));
|
||||
let visibleTasks = $derived(activeTasks.slice(0, wipLimit));
|
||||
let overflowTasks = $derived(activeTasks.slice(wipLimit));
|
||||
let hasOverflow = $derived(overflowTasks.length > 0);
|
||||
|
||||
function handleAdd() {
|
||||
const text = newTaskText.trim();
|
||||
if (!text) return;
|
||||
addTask({ text, bucket: 'inbox' });
|
||||
newTaskText = '';
|
||||
}
|
||||
|
||||
function handleKeydown(e) {
|
||||
if (e.key === 'Enter') handleAdd();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
<!-- Add task input -->
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
bind:value={newTaskText}
|
||||
onkeydown={handleKeydown}
|
||||
placeholder="Add a task…"
|
||||
class="flex-1 px-3 py-2 rounded-lg bg-bg-input border border-border-subtle text-text text-[13px]
|
||||
placeholder:text-text-tertiary focus:border-accent focus:outline-none"
|
||||
data-no-transition
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Active tasks (WIP-limited) -->
|
||||
{#if visibleTasks.length === 0}
|
||||
<p class="text-[12px] text-text-tertiary text-center py-4">No active tasks</p>
|
||||
{:else}
|
||||
<div class="flex flex-col gap-1">
|
||||
{#each visibleTasks as task (task.id)}
|
||||
<div class="flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-hover group"
|
||||
style="transition-duration: var(--duration-ui)">
|
||||
<button
|
||||
class="w-4 h-4 rounded border border-border-subtle flex items-center justify-center flex-shrink-0
|
||||
hover:border-accent"
|
||||
onclick={() => completeTask(task.id)}
|
||||
aria-label="Complete task"
|
||||
>
|
||||
</button>
|
||||
<span class="text-[13px] text-text flex-1 min-w-0 truncate">{task.text}</span>
|
||||
<button
|
||||
class="text-text-tertiary hover:text-danger opacity-0 group-hover:opacity-100 text-[11px]"
|
||||
onclick={() => deleteTask(task.id)}
|
||||
aria-label="Delete task"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Overflow -->
|
||||
{#if hasOverflow}
|
||||
<button
|
||||
class="flex items-center gap-1.5 text-[11px] text-text-tertiary hover:text-text-secondary px-3"
|
||||
onclick={() => showOverflow = !showOverflow}
|
||||
>
|
||||
<ChevronDown size={12} class="transform {showOverflow ? 'rotate-180' : ''}" aria-hidden="true"
|
||||
style="transition: transform var(--duration-ui)" />
|
||||
{overflowTasks.length} more in your list
|
||||
</button>
|
||||
|
||||
{#if showOverflow}
|
||||
<div class="flex flex-col gap-1 opacity-60">
|
||||
{#each overflowTasks as task (task.id)}
|
||||
<div class="flex items-center gap-2 px-3 py-1.5 rounded-lg">
|
||||
<div class="w-3 h-3 rounded border border-border-subtle flex-shrink-0"></div>
|
||||
<span class="text-[12px] text-text-secondary truncate">{task.text}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<!-- WIP indicator -->
|
||||
{#if activeTasks.length > 0}
|
||||
<p class="text-[10px] text-text-tertiary text-right">
|
||||
{Math.min(activeTasks.length, wipLimit)}/{wipLimit} active
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
Reference in New Issue
Block a user