css refactor
This commit is contained in:
@@ -6,19 +6,21 @@
|
||||
* @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';
|
||||
import { page } from "$app/stores";
|
||||
import { t, _ } from "$lib/i18n";
|
||||
|
||||
export let maxVisible = 3;
|
||||
let { maxVisible = 3 } = $props();
|
||||
|
||||
// Breadcrumb items derived from current path
|
||||
$: breadcrumbItems = getBreadcrumbs($page?.url?.pathname || '/', maxVisible);
|
||||
let breadcrumbItems = $derived(
|
||||
getBreadcrumbs($page?.url?.pathname || "/", maxVisible),
|
||||
);
|
||||
|
||||
/**
|
||||
* Generate breadcrumb items from path
|
||||
@@ -26,47 +28,31 @@
|
||||
* @returns {Array} Array of breadcrumb items
|
||||
*/
|
||||
function getBreadcrumbs(pathname, maxVisible = 3) {
|
||||
const segments = pathname.split('/').filter(Boolean);
|
||||
const allItems = [
|
||||
{ label: 'Home', path: '/' }
|
||||
];
|
||||
const segments = pathname.split("/").filter(Boolean);
|
||||
const allItems = [{ label: "Home", path: "/" }];
|
||||
|
||||
let currentPath = '';
|
||||
let currentPath = "";
|
||||
segments.forEach((segment, index) => {
|
||||
currentPath += `/${segment}`;
|
||||
// Convert segment to readable label
|
||||
const label = formatBreadcrumbLabel(segment);
|
||||
allItems.push({
|
||||
label,
|
||||
path: currentPath,
|
||||
isLast: index === segments.length - 1
|
||||
isLast: index === segments.length - 1,
|
||||
});
|
||||
});
|
||||
|
||||
// Handle truncation if too many items
|
||||
// If we have more than maxVisible items, we truncate the middle ones
|
||||
// Always show Home (first) and Current (last)
|
||||
if (allItems.length > maxVisible) {
|
||||
const firstItem = allItems[0];
|
||||
const lastItem = allItems[allItems.length - 1];
|
||||
|
||||
// Calculate how many items we can show in the middle
|
||||
// We reserve 1 for first, 1 for last, and 1 for ellipsis
|
||||
// But ellipsis isn't a real item in terms of logic, it just replaces hidden ones
|
||||
// Actually, let's keep it simple: First ... [Last - (maxVisible - 2) .. Last]
|
||||
|
||||
const itemsToShow = [];
|
||||
itemsToShow.push(firstItem);
|
||||
itemsToShow.push({ isEllipsis: true });
|
||||
|
||||
// Add the last (maxVisible - 2) items
|
||||
// e.g. if maxVisible is 3, we show Start ... End
|
||||
// if maxVisible is 4, we show Start ... SecondLast End
|
||||
const startFromIndex = allItems.length - (maxVisible - 1);
|
||||
for(let i = startFromIndex; i < allItems.length; i++) {
|
||||
itemsToShow.push(allItems[i]);
|
||||
}
|
||||
return itemsToShow;
|
||||
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;
|
||||
@@ -78,63 +64,46 @@
|
||||
* @returns {string} Formatted label
|
||||
*/
|
||||
function formatBreadcrumbLabel(segment) {
|
||||
// Handle special cases
|
||||
const specialCases = {
|
||||
'dashboards': 'nav.dashboard',
|
||||
'datasets': 'nav.tools_mapper',
|
||||
'storage': 'nav.tools_storage',
|
||||
'admin': 'nav.admin',
|
||||
'settings': 'nav.settings',
|
||||
'git': 'nav.git'
|
||||
};
|
||||
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;
|
||||
}
|
||||
|
||||
// Default: capitalize and replace hyphens with spaces
|
||||
return segment
|
||||
.split('-')
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ');
|
||||
.split("-")
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(" ");
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.breadcrumbs {
|
||||
@apply flex items-center space-x-2 text-sm text-gray-600;
|
||||
}
|
||||
|
||||
.breadcrumb-item {
|
||||
@apply flex items-center;
|
||||
}
|
||||
|
||||
.breadcrumb-link {
|
||||
@apply hover:text-blue-600 hover:underline cursor-pointer transition-colors;
|
||||
}
|
||||
|
||||
.breadcrumb-current {
|
||||
@apply text-gray-900 font-medium;
|
||||
}
|
||||
|
||||
.breadcrumb-separator {
|
||||
@apply text-gray-400;
|
||||
}
|
||||
</style>
|
||||
|
||||
<nav class="breadcrumbs" aria-label="Breadcrumb navigation">
|
||||
<nav
|
||||
class="flex items-center space-x-2 text-sm text-gray-600"
|
||||
aria-label="Breadcrumb navigation"
|
||||
>
|
||||
{#each breadcrumbItems as item, index}
|
||||
<div class="breadcrumb-item">
|
||||
<div class="flex items-center">
|
||||
{#if item.isEllipsis}
|
||||
<span class="breadcrumb-separator">...</span>
|
||||
<span class="text-gray-400">...</span>
|
||||
{:else if item.isLast}
|
||||
<span class="breadcrumb-current">{item.label}</span>
|
||||
<span class="text-gray-900 font-medium">{item.label}</span>
|
||||
{:else}
|
||||
<a href={item.path} class="breadcrumb-link">{item.label}</a>
|
||||
<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="breadcrumb-separator">/</span>
|
||||
<span class="text-gray-400">/</span>
|
||||
{/if}
|
||||
{/each}
|
||||
</nav>
|
||||
|
||||
Reference in New Issue
Block a user