Recursive rendering
We can’t see inside our folders. Let’s fix that. Create a folder snippet, using the …
{#snippet folder(f: typeof file.$inferSelect)}
<details>
<summary>
<span class="name">{f.name}</span>
</summary>
<div class="contents">
{@render list(await getFiles(f.id))}
</div>
</details>
{/snippet} …and render it when the type matches our artificial directory type:
{#snippet list(files: (typeof file.$inferSelect)[])}
<div role="list">
{#each files as f (f.id)}
<div role="listitem">
+ {#if f.type === 'application/x-directory'}
+ {@render folder(f)}
+ {:else}
<div class="file">
<FileIcon />
<a href={resolve('/file/[id]', { id: f.id })} class="name">{f.name}</a>
<span class="date">{timeago(f.created_at, new Date())}</span>
<span class="size">{bytes(f.size)}</span>
</div>
+ {/if}
</div>
{/each}
</div>
{/snippet} This works but it looks terrible! Update the .file rule to match summary as well.
We need some folder icons. Import these from Lucide…
import FolderOpenIcon from '@lucide/svelte/icons/folder-open';
import FolderClosedIcon from '@lucide/svelte/icons/folder-closed'; …and put them in the <summary>:
<span class="if-open"><FolderOpenIcon /></span>
<span class="if-closed"><FolderClosedIcon /></span> Add styles:
details {
&[open] > summary > .if-closed {
display: none;
}
&:not([open]) > summary > .if-open {
display: none;
}
} Without indentation, it’s hard to understand the structure. We can fix that with CSS variables:
+{#snippet list(files: (typeof file.$inferSelect)[], depth = 0)}
<div role="list">
{#each files as f (f.id)}
+ <div role="listitem" style:--depth={depth}>
{#if f.type === 'application/x-directory'}
+ {@render folder(f, depth)}
{:else}
<div class="file">
<FileIcon />
<a href={resolve('/file/[id]', { id: f.id })} class="name">{f.name}</a>
<span class="date">{timeago(f.created_at, new Date())}</span>
<span class="size">{bytes(f.size)}</span>
</div>
{/if}
</div>
{/each}
</div>
{/snippet}
+{#snippet folder(f: typeof file.$inferSelect, depth: number)}
<details>
<summary>
<span class="if-open"><FolderOpenIcon /></span>
<span class="if-closed"><FolderClosedIcon /></span>
<span class="name">{f.name}</span>
</summary>
<div class="contents">
+ {@render list(await getFiles(f.id), depth + 1)}
</div>
</details>
{/snippet} Update the padding on the summary, .file rule to use the variable:
padding: 0 1.2rem 0 calc(1.2rem + 0.5rem * var(--depth));