Time to combine every pattern we've covered into a midterm learner dashboard.
Quick recap
- What we're building: the
/dashboardscreen where a sidebar, content cards, and bottom log stay visible together, plus the CRUD flow behind each task card. - Why it matters: the mini dashboard is a checkpoint showing how layout, state, forms, and animations interlock.
- Watch out for: lock in the slot-based layout first and double-check
depends/invalidatekeys so every panel refreshes in sync.
Key terms
- Dashboard: a single screen that shows multiple metric cards and forms at once so the team sees status immediately.
- Slot layout: a layout that reserves regions like sidebar, main, and bottom log, then swaps only the content inside each slot.
Core ideas
A dashboard is a status board. Slot layouts keep regions fixed so you can drop in new pages without rewriting the shell. Stores and load supply the initial data. Form actions plus optimistic UI reflect user input immediately. Transitions and spring motion smooth out state changes so attention stays on the content. Consider this the series midterm before we dive into auth, files, and live features.
Code examples
The root layout, page data, card list, and form work together.
<!-- src/routes/(app)/+layout.svelte -->
<AppShell>
<svelte:fragment slot="sidebar">
<SubjectSidebar />
</svelte:fragment>
<slot />
<svelte:fragment slot="bottom-panel">
<ActivityLog />
</svelte:fragment>
</AppShell>
// src/routes/(app)/dashboard/+page.server.ts
export const load = async ({ fetch, depends }) => {
depends('data:tasks');
const [subjects, tasks] = await Promise.all([
fetch('/api/subjects').then((r) => r.json()),
fetch('/api/tasks').then((r) => r.json())
]);
return { subjects, tasks };
};
<!-- Card list -->
<section class="grid" animate:flip>
{#if filtered.length === 0}
<EmptyState in:fade={{ duration: 120 }} />
{:else}
{#each filtered as task (task.id)}
<TaskCard {task} transition:scale={{ duration: 120 }} on:toggle />
{/each}
{/if}
</section>
<form method="POST" action="?/create"
use:enhance={({ result }) => {
if (result.type === 'success') invalidate('data:tasks');
}}>
<input name="title" placeholder="New task" required />
<select name="subjectId">
{#each subjects as subject}
<option value={subject.id}>{subject.name}</option>
{/each}
</select>
<button>Add</button>
</form>
Toggle cards with the optimistic pattern from part 11: update state immediately, hold a snapshot, and roll back on failure.
💬 댓글
이 글에 대한 의견을 남겨주세요