feat(Breadcrumb): create new entity that contains logic related to breadcrumb-like navigation
This commit is contained in:
2
src/entities/Breadcrumb/index.ts
Normal file
2
src/entities/Breadcrumb/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { scrollBreadcrumbsStore } from './model';
|
||||||
|
export { BreadcrumbHeader } from './ui';
|
||||||
1
src/entities/Breadcrumb/model/index.ts
Normal file
1
src/entities/Breadcrumb/model/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './store/scrollBreadcrumbsStore.svelte';
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
|
export interface BreadcrumbItem {
|
||||||
|
index: number;
|
||||||
|
title: Snippet<[{ className?: string }]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScrollBreadcrumbsStore {
|
||||||
|
#items = $state<BreadcrumbItem[]>([]);
|
||||||
|
|
||||||
|
get items() {
|
||||||
|
// Keep them sorted by index for Swiss orderliness
|
||||||
|
return this.#items.sort((a, b) => a.index - b.index);
|
||||||
|
}
|
||||||
|
add(item: BreadcrumbItem) {
|
||||||
|
if (!this.#items.find(i => i.index === item.index)) {
|
||||||
|
this.#items.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
remove(index: number) {
|
||||||
|
this.#items = this.#items.filter(i => i.index !== index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createScrollBreadcrumbsStore() {
|
||||||
|
return new ScrollBreadcrumbsStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const scrollBreadcrumbsStore = createScrollBreadcrumbsStore();
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<!--
|
||||||
|
Component: BreadcrumbHeader
|
||||||
|
Fixed header for breadcrumbs navigation for sections in the page
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import Icon from '@lucide/svelte/icons/align-vertical-justify-center';
|
||||||
|
import { flip } from 'svelte/animate';
|
||||||
|
import { slide } from 'svelte/transition';
|
||||||
|
import { scrollBreadcrumbsStore } from '../../model';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if scrollBreadcrumbsStore.items.length > 0}
|
||||||
|
<div
|
||||||
|
transition:slide={{ duration: 200 }}
|
||||||
|
class="
|
||||||
|
fixed top-0 left-0 right-0 z-100
|
||||||
|
backdrop-blur-lg bg-white/20
|
||||||
|
border-b border-gray-300/50
|
||||||
|
shadow-[0_1px_3px_rgba(0,0,0,0.04)]
|
||||||
|
h-12
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div class="max-w-8xl mx-auto px-6 h-full flex items-center gap-4">
|
||||||
|
<div class="flex items-center gap-2.5 opacity-70">
|
||||||
|
<Icon class="size-4 stroke-gray-900 stroke-1" />
|
||||||
|
<div class="w-px h-2.5 bg-gray-400/50"></div>
|
||||||
|
<span class="font-mono text-[9px] uppercase tracking-[0.25em] text-gray-500 font-medium">
|
||||||
|
nav_trace
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="h-4 w-px bg-gray-300/60"></div>
|
||||||
|
|
||||||
|
<nav class="flex items-center gap-3 overflow-x-auto scrollbar-hide flex-1">
|
||||||
|
{#each scrollBreadcrumbsStore.items as item, idx (item.index)}
|
||||||
|
<div
|
||||||
|
animate:flip={{ duration: 200 }}
|
||||||
|
class="flex items-center gap-3 whitespace-nowrap shrink-0"
|
||||||
|
>
|
||||||
|
<span class="font-mono text-[9px] text-gray-400 tracking-wider">
|
||||||
|
{String(item.index).padStart(2, '0')}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{@render item.title({
|
||||||
|
className: 'font-mono text-[10px] font-bold uppercase tracking-tight leading-[0.95] text-gray-900',
|
||||||
|
})}
|
||||||
|
|
||||||
|
{#if idx < scrollBreadcrumbsStore.items.length - 1}
|
||||||
|
<div class="flex items-center gap-0.5 opacity-40">
|
||||||
|
<div class="w-1 h-px bg-gray-400"></div>
|
||||||
|
<div class="w-1 h-px bg-gray-400"></div>
|
||||||
|
<div class="w-1 h-px bg-gray-400"></div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2 opacity-50 ml-auto">
|
||||||
|
<div class="w-px h-2.5 bg-gray-300/60"></div>
|
||||||
|
<span class="font-mono text-[8px] text-gray-400 tracking-wider">
|
||||||
|
[{scrollBreadcrumbsStore.items.length}]
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Hide scrollbar but keep functionality */
|
||||||
|
.scrollbar-hide {
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
.scrollbar-hide::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
3
src/entities/Breadcrumb/ui/index.ts
Normal file
3
src/entities/Breadcrumb/ui/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import BreadcrumbHeader from './BreadcrumbHeader/BreadcrumbHeader.svelte';
|
||||||
|
|
||||||
|
export { BreadcrumbHeader };
|
||||||
Reference in New Issue
Block a user