- Added Tailwind CSS for styling. - Implemented automatic navigation data generation from markdown files. - Created `NavItem` component for rendering navigation items. - Added scripts for navigation generation and updated build process.
166 lines
4.5 KiB
Svelte
166 lines
4.5 KiB
Svelte
<script lang="ts">
|
|
import type { NavItem } from "../types/nav";
|
|
import { ChevronRight, ChevronDown } from "lucide-svelte";
|
|
import { slide } from "svelte/transition";
|
|
|
|
export let item: NavItem;
|
|
export let level: number = 0;
|
|
export let expanded: Record<string, boolean> = {};
|
|
export let activePath: string = "";
|
|
export let onToggle: (path: string) => void;
|
|
export let onNavClick: (path: string, event: MouseEvent) => void;
|
|
|
|
$: hasChildren = item.children && item.children.length > 0;
|
|
$: isExpanded = expanded[item.link];
|
|
$: isActive = activePath === item.link;
|
|
|
|
// Calculate indentation based on nesting level
|
|
$: indentation = level * 16; // 16px per level
|
|
|
|
function handleKeyDown(event: KeyboardEvent, isFolder: boolean) {
|
|
// Add keyboard navigation
|
|
if (isFolder && (event.key === "Enter" || event.key === " ")) {
|
|
event.preventDefault();
|
|
onToggle(item.link);
|
|
} else if (!isFolder && event.key === "Enter") {
|
|
event.preventDefault();
|
|
onNavClick(item.link, event as unknown as MouseEvent);
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<div class="nav-item" style="--indent: {indentation}px;">
|
|
{#if hasChildren}
|
|
<!-- Folder item -->
|
|
<button
|
|
class="nav-button folder-button {isActive ? 'active' : ''}"
|
|
on:click={() => onToggle(item.link)}
|
|
on:keydown={(e) => handleKeyDown(e, true)}
|
|
tabindex="0"
|
|
aria-expanded={isExpanded}
|
|
>
|
|
<div class="tree-line" style="width: {indentation}px;"></div>
|
|
<div class="icon-container">
|
|
{#if isExpanded}
|
|
<ChevronDown
|
|
class="chevron-icon {isExpanded ? 'expanded' : ''}"
|
|
/>
|
|
{:else}
|
|
<ChevronRight
|
|
class="chevron-icon {isExpanded ? 'expanded' : ''}"
|
|
/>
|
|
{/if}
|
|
</div>
|
|
<span class="label">{item.label}</span>
|
|
</button>
|
|
|
|
{#if isExpanded && item.children && item.children.length > 0}
|
|
<div class="children" transition:slide={{ duration: 200 }}>
|
|
{#each item.children as child}
|
|
<svelte:self
|
|
item={child}
|
|
level={level + 1}
|
|
{expanded}
|
|
{activePath}
|
|
{onToggle}
|
|
{onNavClick}
|
|
/>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
{:else}
|
|
<!-- File item -->
|
|
<a
|
|
href={item.link}
|
|
class="nav-button file-button {isActive ? 'active' : ''}"
|
|
on:click={(e) => onNavClick(item.link, e)}
|
|
on:keydown={(e) => handleKeyDown(e, false)}
|
|
tabindex="0"
|
|
>
|
|
<div class="tree-line" style="width: {indentation}px;"></div>
|
|
<div class="icon-container">
|
|
<!-- No file icon -->
|
|
</div>
|
|
<span class="label">{item.label}</span>
|
|
</a>
|
|
{/if}
|
|
</div>
|
|
|
|
<style>
|
|
.nav-item {
|
|
position: relative;
|
|
}
|
|
|
|
.nav-button {
|
|
display: flex;
|
|
align-items: center;
|
|
width: 100%;
|
|
text-align: left;
|
|
padding: 8px 12px;
|
|
padding-left: calc(var(--indent) + 12px);
|
|
position: relative;
|
|
border: none;
|
|
background: transparent;
|
|
color: #4b5563;
|
|
font-size: 0.9rem;
|
|
cursor: pointer;
|
|
transition:
|
|
background-color 0.2s,
|
|
color 0.2s;
|
|
}
|
|
|
|
.nav-button:hover {
|
|
background-color: rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.nav-button.active {
|
|
background-color: rgba(59, 130, 246, 0.1);
|
|
color: #3b82f6;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.tree-line {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
bottom: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.tree-line::before {
|
|
content: "";
|
|
position: absolute;
|
|
left: 12px;
|
|
top: 0;
|
|
bottom: 0;
|
|
width: 1px;
|
|
background-color: #e5e7eb;
|
|
}
|
|
|
|
.icon-container {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-right: 8px;
|
|
}
|
|
|
|
.chevron-icon {
|
|
width: 16px;
|
|
height: 16px;
|
|
transition: transform 0.2s;
|
|
}
|
|
|
|
.chevron-icon.expanded {
|
|
transform: rotate(90deg);
|
|
}
|
|
|
|
.label {
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.children {
|
|
overflow: hidden;
|
|
}
|
|
</style>
|