Files
ss-tools/frontend/src/lib/components/layout/Breadcrumbs.svelte
2026-02-19 18:24:36 +03:00

112 lines
2.9 KiB
Svelte

<!-- [DEF:Breadcrumbs:Component] -->
<script>
/**
* @TIER: STANDARD
* @PURPOSE: Display page hierarchy navigation
* @LAYER: UI
* @RELATION: DEPENDS_ON -> page store
* @INVARIANT: Always shows current page path
*
* @UX_STATE: Idle -> Breadcrumbs showing current path
* @UX_FEEDBACK: Hover on breadcrumb shows clickable state
* @UX_RECOVERY: Click breadcrumb to navigate
*/
import { page } from "$app/stores";
import { t, _ } from "$lib/i18n";
let { maxVisible = 3 } = $props();
// Breadcrumb items derived from current path
let breadcrumbItems = $derived(
getBreadcrumbs($page?.url?.pathname || "/", maxVisible),
);
/**
* Generate breadcrumb items from path
* @param {string} pathname - Current path
* @returns {Array} Array of breadcrumb items
*/
function getBreadcrumbs(pathname, maxVisible = 3) {
const segments = pathname.split("/").filter(Boolean);
const allItems = [{ label: "Home", path: "/" }];
let currentPath = "";
segments.forEach((segment, index) => {
currentPath += `/${segment}`;
const label = formatBreadcrumbLabel(segment);
allItems.push({
label,
path: currentPath,
isLast: index === segments.length - 1,
});
});
if (allItems.length > maxVisible) {
const firstItem = allItems[0];
const itemsToShow = [];
itemsToShow.push(firstItem);
itemsToShow.push({ isEllipsis: true });
const startFromIndex = allItems.length - (maxVisible - 1);
for (let i = startFromIndex; i < allItems.length; i++) {
itemsToShow.push(allItems[i]);
}
return itemsToShow;
}
return allItems;
}
/**
* Format segment to readable label
* @param {string} segment - URL segment
* @returns {string} Formatted label
*/
function formatBreadcrumbLabel(segment) {
const specialCases = {
dashboards: "nav.dashboard",
datasets: "nav.tools_mapper",
storage: "nav.tools_storage",
admin: "nav.admin",
settings: "nav.settings",
git: "nav.git",
};
if (specialCases[segment]) {
return _(specialCases[segment]) || segment;
}
return segment
.split("-")
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ");
}
</script>
<nav
class="flex items-center space-x-2 text-sm text-gray-600"
aria-label="Breadcrumb navigation"
>
{#each breadcrumbItems as item, index}
<div class="flex items-center">
{#if item.isEllipsis}
<span class="text-gray-400">...</span>
{:else if item.isLast}
<span class="text-gray-900 font-medium">{item.label}</span>
{:else}
<a
href={item.path}
class="hover:text-primary hover:underline cursor-pointer transition-colors"
>{item.label}</a
>
{/if}
</div>
{#if index < breadcrumbItems.length - 1}
<span class="text-gray-400">/</span>
{/if}
{/each}
</nav>
<!-- [/DEF:Breadcrumbs:Component] -->