Finalize task-020 reports navigation and stability fixes

This commit is contained in:
2026-02-23 13:28:30 +03:00
parent 26880d2e09
commit d0ffc2f1df
8 changed files with 275 additions and 240 deletions

View File

@@ -2,7 +2,8 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" /> <link rel="icon" type="image/svg+xml" href="%sveltekit.assets%/favicon.svg" />
<link rel="alternate icon" type="image/png" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head% %sveltekit.head%
</head> </head>

View File

@@ -14,6 +14,7 @@
import { page } from "$app/stores"; import { page } from "$app/stores";
import { t, _ } from "$lib/i18n"; import { t, _ } from "$lib/i18n";
import Icon from "$lib/ui/Icon.svelte";
let { maxVisible = 3 } = $props(); let { maxVisible = 3 } = $props();
@@ -82,30 +83,103 @@
.map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(" "); .join(" ");
} }
function getCrumbMeta(item) {
if (item.path === "/") {
return {
icon: "home",
tone: "from-sky-100 to-cyan-100 text-sky-700 ring-sky-200",
};
}
const segment = item.path.split("/").filter(Boolean).at(-1) || "";
const map = {
dashboards: {
icon: "dashboard",
tone: "from-sky-100 to-sky-200 text-sky-700 ring-sky-200",
},
datasets: {
icon: "database",
tone: "from-emerald-100 to-emerald-200 text-emerald-700 ring-emerald-200",
},
storage: {
icon: "storage",
tone: "from-amber-100 to-amber-200 text-amber-800 ring-amber-200",
},
reports: {
icon: "reports",
tone: "from-violet-100 to-fuchsia-100 text-violet-700 ring-violet-200",
},
admin: {
icon: "admin",
tone: "from-rose-100 to-rose-200 text-rose-700 ring-rose-200",
},
settings: {
icon: "settings",
tone: "from-slate-100 to-slate-200 text-slate-700 ring-slate-200",
},
git: {
icon: "storage",
tone: "from-orange-100 to-orange-200 text-orange-700 ring-orange-200",
},
};
return (
map[segment] || {
icon: "layers",
tone: "from-slate-100 to-slate-200 text-slate-600 ring-slate-200",
}
);
}
</script> </script>
<nav <nav
class="flex items-center space-x-2 text-sm text-gray-600" class="mx-4 md:mx-6"
aria-label="Breadcrumb navigation" aria-label="Breadcrumb navigation"
> >
{#each breadcrumbItems as item, index} <div class="inline-flex max-w-full items-center gap-1.5 rounded-xl border border-slate-200/80 bg-white/85 px-2 py-1.5 shadow-sm backdrop-blur">
<div class="flex items-center"> {#each breadcrumbItems as item, index}
{#if item.isEllipsis} <div class="flex min-w-0 items-center gap-1.5">
<span class="text-gray-400">...</span> {#if item.isEllipsis}
{:else if item.isLast} <span class="px-2 py-1 text-xs font-semibold tracking-wide text-slate-400"
<span class="text-gray-900 font-medium">{item.label}</span> >...</span
{:else} >
<a {:else}
href={item.path} {@const meta = getCrumbMeta(item)}
class="hover:text-primary hover:underline cursor-pointer transition-colors" {#if item.isLast}
>{item.label}</a <span
> class="inline-flex min-w-0 items-center gap-2 rounded-lg bg-slate-900 px-2.5 py-1.5 text-sm font-medium text-white"
>
<span
class="inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-md bg-white/10"
>
<Icon name={meta.icon} size={12} strokeWidth={2.1} />
</span>
<span class="truncate">{item.label}</span>
</span>
{:else}
<a
href={item.path}
class="inline-flex min-w-0 items-center gap-2 rounded-lg px-2.5 py-1.5 text-sm text-slate-700 ring-1 ring-transparent transition-all hover:bg-slate-50 hover:ring-slate-200"
>
<span
class="inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-md bg-gradient-to-br ring-1 {meta.tone}"
>
<Icon name={meta.icon} size={12} strokeWidth={2.1} />
</span>
<span class="truncate">{item.label}</span>
</a>
{/if}
{/if}
</div>
{#if index < breadcrumbItems.length - 1}
<span class="text-slate-300">
<Icon name="chevronRight" size={14} strokeWidth={2.1} />
</span>
{/if} {/if}
</div> {/each}
{#if index < breadcrumbItems.length - 1} </div>
<span class="text-gray-400">/</span>
{/if}
{/each}
</nav> </nav>
<!-- [/DEF:Breadcrumbs:Component] --> <!-- [/DEF:Breadcrumbs:Component] -->

View File

@@ -24,59 +24,68 @@
} from "$lib/stores/sidebar.js"; } from "$lib/stores/sidebar.js";
import { t } from "$lib/i18n"; import { t } from "$lib/i18n";
import { browser } from "$app/environment"; import { browser } from "$app/environment";
import Icon from "$lib/ui/Icon.svelte";
// Sidebar categories with sub-items matching Superset-style navigation function buildCategories() {
let categories = [ return [
{ {
id: "dashboards", id: "dashboards",
label: $t.nav?.dashboards || "DASHBOARDS", label: $t.nav?.dashboards || "DASHBOARDS",
icon: "M3 3h18v18H3V3zm16 16V5H5v14h14z", icon: "dashboard",
path: "/dashboards", tone: "from-sky-100 to-sky-200 text-sky-700 ring-sky-200",
subItems: [ path: "/dashboards",
{ label: $t.nav?.overview || "Overview", path: "/dashboards" }, subItems: [
], { label: $t.nav?.overview || "Overview", path: "/dashboards" },
}, ],
{ },
id: "datasets", {
label: $t.nav?.datasets || "DATASETS", id: "datasets",
icon: "M3 3h18v18H3V3zm2 2v14h14V5H5zm2 2h10v2H7V7zm0 4h10v2H7v-2zm0 4h6v2H7v-2z", label: $t.nav?.datasets || "DATASETS",
path: "/datasets", icon: "database",
subItems: [ tone: "from-emerald-100 to-emerald-200 text-emerald-700 ring-emerald-200",
{ label: $t.nav?.all_datasets || "All Datasets", path: "/datasets" }, path: "/datasets",
], subItems: [
}, { label: $t.nav?.all_datasets || "All Datasets", path: "/datasets" },
{ ],
id: "storage", },
label: $t.nav?.storage || "STORAGE", {
icon: "M4 4h16v16H4V4zm2 2v12h12V6H6zm2 2h8v2H8V8zm0 4h8v2H8v-2zm0 4h5v2H8v-2z", id: "storage",
path: "/storage", label: $t.nav?.storage || "STORAGE",
subItems: [ icon: "storage",
{ label: $t.nav?.backups || "Backups", path: "/storage/backups" }, tone: "from-amber-100 to-amber-200 text-amber-800 ring-amber-200",
{ path: "/storage",
label: $t.nav?.repositories || "Repositories", subItems: [
path: "/storage/repos", { label: $t.nav?.backups || "Backups", path: "/storage/backups" },
}, {
], label: $t.nav?.repositories || "Repositories",
}, path: "/storage/repos",
{ },
id: "reports", ],
label: $t.nav?.reports || "REPORTS", },
icon: "M4 5h16M4 12h16M4 19h10", {
path: "/reports", id: "reports",
subItems: [{ label: $t.nav?.reports || "Reports", path: "/reports" }], label: $t.nav?.reports || "REPORTS",
}, icon: "reports",
{ tone: "from-violet-100 to-fuchsia-100 text-violet-700 ring-violet-200",
id: "admin", path: "/reports",
label: $t.nav?.admin || "ADMIN", subItems: [{ label: $t.nav?.reports || "Reports", path: "/reports" }],
icon: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z", },
path: "/admin", {
subItems: [ id: "admin",
{ label: $t.nav?.admin_users || "Users", path: "/admin/users" }, label: $t.nav?.admin || "ADMIN",
{ label: $t.nav?.admin_roles || "Roles", path: "/admin/roles" }, icon: "admin",
{ label: $t.nav?.settings || "Settings", path: "/settings" }, tone: "from-rose-100 to-rose-200 text-rose-700 ring-rose-200",
], path: "/admin",
}, subItems: [
]; { label: $t.nav?.admin_users || "Users", path: "/admin/users" },
{ label: $t.nav?.admin_roles || "Roles", path: "/admin/roles" },
{ label: $t.nav?.settings || "Settings", path: "/settings" },
],
},
];
}
let categories = buildCategories();
let isExpanded = true; let isExpanded = true;
let activeCategory = "dashboards"; let activeCategory = "dashboards";
@@ -93,57 +102,7 @@
} }
// Reactive categories to update translations // Reactive categories to update translations
$: categories = [ $: categories = buildCategories();
{
id: "dashboards",
label: $t.nav?.dashboards || "DASHBOARDS",
icon: "M3 3h18v18H3V3zm16 16V5H5v14h14z",
path: "/dashboards",
subItems: [
{ label: $t.nav?.overview || "Overview", path: "/dashboards" },
],
},
{
id: "datasets",
label: $t.nav?.datasets || "DATASETS",
icon: "M3 3h18v18H3V3zm2 2v14h14V5H5zm2 2h10v2H7V7zm0 4h10v2H7v-2zm0 4h6v2H7v-2z",
path: "/datasets",
subItems: [
{ label: $t.nav?.all_datasets || "All Datasets", path: "/datasets" },
],
},
{
id: "storage",
label: $t.nav?.storage || "STORAGE",
icon: "M4 4h16v16H4V4zm2 2v12h12V6H6zm2 2h8v2H8V8zm0 4h8v2H8v-2zm0 4h5v2H8v-2z",
path: "/storage",
subItems: [
{ label: $t.nav?.backups || "Backups", path: "/storage/backups" },
{
label: $t.nav?.repositories || "Repositories",
path: "/storage/repos",
},
],
},
{
id: "reports",
label: $t.nav?.reports || "REPORTS",
icon: "M4 5h16M4 12h16M4 19h10",
path: "/reports",
subItems: [{ label: $t.nav?.reports || "Reports", path: "/reports" }],
},
{
id: "admin",
label: $t.nav?.admin || "ADMIN",
icon: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z",
path: "/admin",
subItems: [
{ label: $t.nav?.admin_users || "Users", path: "/admin/users" },
{ label: $t.nav?.admin_roles || "Roles", path: "/admin/roles" },
{ label: $t.nav?.settings || "Settings", path: "/settings" },
],
},
];
// Update active item when page changes // Update active item when page changes
$: if ($page && $page.url.pathname !== activeItem) { $: if ($page && $page.url.pathname !== activeItem) {
@@ -238,7 +197,12 @@
: 'justify-center'}" : 'justify-center'}"
> >
{#if isExpanded} {#if isExpanded}
<span class="font-semibold text-gray-800">Menu</span> <span class="font-semibold text-gray-800 flex items-center gap-2">
<span class="inline-flex h-6 w-6 items-center justify-center rounded-md bg-gradient-to-br from-slate-100 to-slate-200 text-slate-700 ring-1 ring-slate-200">
<Icon name="layers" size={14} />
</span>
Menu
</span>
{:else} {:else}
<span class="text-xs text-gray-500">M</span> <span class="text-xs text-gray-500">M</span>
{/if} {/if}
@@ -264,16 +228,9 @@
aria-expanded={expandedCategories.has(category.id)} aria-expanded={expandedCategories.has(category.id)}
> >
<div class="flex items-center"> <div class="flex items-center">
<svg <span class="inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br ring-1 transition-all {category.tone}">
class="w-5 h-5 shrink-0" <Icon name={category.icon} size={16} strokeWidth={2} />
xmlns="http://www.w3.org/2000/svg" </span>
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d={category.icon} />
</svg>
{#if isExpanded} {#if isExpanded}
<span class="ml-3 text-sm font-medium truncate" <span class="ml-3 text-sm font-medium truncate"
>{category.label}</span >{category.label}</span
@@ -281,22 +238,15 @@
{/if} {/if}
</div> </div>
{#if isExpanded} {#if isExpanded}
<svg <Icon
name="chevronDown"
size={16}
class="text-gray-400 transition-transform duration-200 {expandedCategories.has( class="text-gray-400 transition-transform duration-200 {expandedCategories.has(
category.id, category.id,
) )
? 'rotate-180' ? 'rotate-180'
: ''}" : ''}"
xmlns="http://www.w3.org/2000/svg" />
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M6 9l6 6 6-6" />
</svg>
{/if} {/if}
</div> </div>
@@ -332,18 +282,9 @@
class="flex items-center justify-center w-full px-4 py-2 text-sm text-gray-600 hover:bg-gray-100 rounded-lg transition-colors" class="flex items-center justify-center w-full px-4 py-2 text-sm text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
on:click={handleToggleClick} on:click={handleToggleClick}
> >
<svg <span class="mr-2 inline-flex h-6 w-6 items-center justify-center rounded-md bg-slate-100 text-slate-600">
xmlns="http://www.w3.org/2000/svg" <Icon name="chevronLeft" size={14} />
width="16" </span>
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
class="mr-2"
>
<path d="M15 18l-6-6 6-6" />
</svg>
Collapse Collapse
</button> </button>
</div> </div>
@@ -354,17 +295,7 @@
on:click={handleToggleClick} on:click={handleToggleClick}
aria-label="Expand sidebar" aria-label="Expand sidebar"
> >
<svg <Icon name="chevronRight" size={16} />
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M9 18l6-6-6-6" />
</svg>
<span class="ml-2">Expand</span> <span class="ml-2">Expand</span>
</button> </button>
</div> </div>

View File

@@ -24,6 +24,7 @@
import PasswordPrompt from "../../../components/PasswordPrompt.svelte"; import PasswordPrompt from "../../../components/PasswordPrompt.svelte";
import { t } from "$lib/i18n"; import { t } from "$lib/i18n";
import { api } from "$lib/api.js"; import { api } from "$lib/api.js";
import Icon from "$lib/ui/Icon.svelte";
let isOpen = false; let isOpen = false;
let activeTaskId = null; let activeTaskId = null;
@@ -209,9 +210,7 @@
<div class="flex items-center gap-2.5"> <div class="flex items-center gap-2.5">
{#if !activeTaskId && recentTasks.length > 0} {#if !activeTaskId && recentTasks.length > 0}
<span class="flex items-center justify-center p-1.5 mr-1 text-cyan-400"> <span class="flex items-center justify-center p-1.5 mr-1 text-cyan-400">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <Icon name="list" size={16} strokeWidth={2} />
<path d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01"/>
</svg>
</span> </span>
{:else if activeTaskId} {:else if activeTaskId}
<button <button
@@ -219,17 +218,7 @@
on:click={goBackToList} on:click={goBackToList}
aria-label="Back to task list" aria-label="Back to task list"
> >
<svg <Icon name="back" size={16} strokeWidth={2} />
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M19 12H5M12 19l-7-7 7-7" />
</svg>
</button> </button>
{/if} {/if}
<h2 class="text-sm font-semibold text-slate-100 tracking-tight"> <h2 class="text-sm font-semibold text-slate-100 tracking-tight">
@@ -256,17 +245,7 @@
on:click={handleClose} on:click={handleClose}
aria-label="Close drawer" aria-label="Close drawer"
> >
<svg <Icon name="close" size={18} strokeWidth={2} />
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M18 6L6 18M6 6l12 12" />
</svg>
</button> </button>
</div> </div>
</div> </div>
@@ -301,18 +280,12 @@
</div> </div>
{:else} {:else}
<div class="flex flex-col items-center justify-center h-full text-slate-500"> <div class="flex flex-col items-center justify-center h-full text-slate-500">
<svg <Icon
class="w-12 h-12 mb-3 text-slate-700" name="clipboard"
xmlns="http://www.w3.org/2000/svg" size={48}
viewBox="0 0 24 24" strokeWidth={1.6}
fill="none" className="mb-3 text-slate-700"
stroke="currentColor" />
stroke-width="1.5"
>
<path
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"
/>
</svg>
<p>{$t.tasks?.select_task || 'No recent tasks'}</p> <p>{$t.tasks?.select_task || 'No recent tasks'}</p>
</div> </div>
{/if} {/if}
@@ -330,4 +303,3 @@
{/if} {/if}
<!-- [/DEF:TaskDrawer:Component] --> <!-- [/DEF:TaskDrawer:Component] -->

View File

@@ -25,6 +25,7 @@
import { sidebarStore, toggleMobileSidebar } from "$lib/stores/sidebar.js"; import { sidebarStore, toggleMobileSidebar } from "$lib/stores/sidebar.js";
import { t } from "$lib/i18n"; import { t } from "$lib/i18n";
import { auth } from "$lib/auth/store.js"; import { auth } from "$lib/auth/store.js";
import Icon from "$lib/ui/Icon.svelte";
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
@@ -99,19 +100,7 @@
on:click={handleHamburgerClick} on:click={handleHamburgerClick}
aria-label="Toggle menu" aria-label="Toggle menu"
> >
<svg <Icon name="menu" size={22} />
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button> </button>
<!-- Logo/Brand --> <!-- Logo/Brand -->
@@ -119,14 +108,9 @@
href="/" href="/"
class="flex items-center text-xl font-bold text-gray-800 hover:text-primary transition-colors" class="flex items-center text-xl font-bold text-gray-800 hover:text-primary transition-colors"
> >
<svg <span class="mr-2 inline-flex h-9 w-9 items-center justify-center rounded-xl bg-gradient-to-br from-sky-500 via-cyan-500 to-indigo-600 text-white shadow-sm">
class="w-8 h-8 mr-2 text-primary" <Icon name="layers" size={18} strokeWidth={2.1} />
xmlns="http://www.w3.org/2000/svg" </span>
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
</svg>
<span>Superset Tools</span> <span>Superset Tools</span>
</a> </a>
</div> </div>
@@ -147,7 +131,7 @@
<div class="flex items-center space-x-4"> <div class="flex items-center space-x-4">
<!-- Activity Indicator --> <!-- Activity Indicator -->
<div <div
class="relative cursor-pointer p-2 rounded-lg hover:bg-gray-100 transition-colors" class="relative cursor-pointer p-2 rounded-lg hover:bg-gray-100 transition-colors text-slate-600"
on:click={handleActivityClick} on:click={handleActivityClick}
on:keydown={(e) => on:keydown={(e) =>
(e.key === "Enter" || e.key === " ") && handleActivityClick()} (e.key === "Enter" || e.key === " ") && handleActivityClick()}
@@ -155,18 +139,7 @@
tabindex="0" tabindex="0"
aria-label="Activity" aria-label="Activity"
> >
<svg <Icon name="activity" size={22} />
class="w-6 h-6 text-gray-600"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"
/>
</svg>
{#if activeCount > 0} {#if activeCount > 0}
<span <span
class="absolute -top-1 -right-1 bg-destructive text-white text-xs font-bold rounded-full w-5 h-5 flex items-center justify-center" class="absolute -top-1 -right-1 bg-destructive text-white text-xs font-bold rounded-full w-5 h-5 flex items-center justify-center"

View File

@@ -0,0 +1,66 @@
<script>
export let name = "circle";
export let size = 20;
export let className = "";
export let strokeWidth = 1.9;
const iconPaths = {
home: ["M3 11l9-7 9 7", "M5 10v9h14v-9", "M10 19v-5h4v5"],
dashboard: ["M4 4h16v16H4z", "M4 10h16", "M10 4v16"],
database: [
"M4 7c0-1.7 3.6-3 8-3s8 1.3 8 3-3.6 3-8 3-8-1.3-8-3z",
"M4 12c0 1.7 3.6 3 8 3s8-1.3 8-3",
"M4 17c0 1.7 3.6 3 8 3s8-1.3 8-3",
"M4 7v10",
"M20 7v10",
],
storage: [
"M3 8l9-4 9 4-9 4-9-4z",
"M3 13l9 4 9-4",
"M3 17l9 4 9-4",
],
reports: ["M5 5h14v14H5z", "M8 9h8", "M8 13h8", "M8 17h5"],
admin: ["M12 3l8 4v5c0 5.2-3.4 8.6-8 9.9C7.4 20.6 4 17.2 4 12V7l8-4z", "M9 12l2 2 4-4"],
chevronDown: ["M6 9l6 6 6-6"],
chevronLeft: ["M15 6l-6 6 6 6"],
chevronRight: ["M9 6l6 6-6 6"],
menu: ["M4 7h16", "M4 12h16", "M4 17h16"],
activity: [
"M12 3v3",
"M12 18v3",
"M4.9 4.9l2.1 2.1",
"M17 17l2.1 2.1",
"M3 12h3",
"M18 12h3",
"M4.9 19.1L7 17",
"M17 7l2.1-2.1",
"M12 15a3 3 0 100-6 3 3 0 000 6z",
],
layers: ["M12 4l8 4-8 4-8-4 8-4z", "M4 12l8 4 8-4", "M4 16l8 4 8-4"],
back: ["M19 12H5", "M12 5l-7 7 7 7"],
close: ["M18 6L6 18", "M6 6l12 12"],
list: ["M8 7h12", "M8 12h12", "M8 17h12", "M4 7h.01", "M4 12h.01", "M4 17h.01"],
clipboard: ["M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2", "M9 5a2 2 0 002 2h2a2 2 0 002-2", "M9 5a2 2 0 012-2h2a2 2 0 012 2"],
settings: ["M12 8.5a3.5 3.5 0 100 7 3.5 3.5 0 000-7z", "M19.4 15a1 1 0 00.2 1.1l.1.1a1 1 0 010 1.4l-1.1 1.1a1 1 0 01-1.4 0l-.1-.1a1 1 0 00-1.1-.2 1 1 0 00-.6.9V20a1 1 0 01-1 1h-1.6a1 1 0 01-1-1v-.2a1 1 0 00-.6-.9 1 1 0 00-1.1.2l-.1.1a1 1 0 01-1.4 0l-1.1-1.1a1 1 0 010-1.4l.1-.1a1 1 0 00.2-1.1 1 1 0 00-.9-.6H4a1 1 0 01-1-1v-1.6a1 1 0 011-1h.2a1 1 0 00.9-.6 1 1 0 00-.2-1.1l-.1-.1a1 1 0 010-1.4l1.1-1.1a1 1 0 011.4 0l.1.1a1 1 0 001.1.2 1 1 0 00.6-.9V4a1 1 0 011-1h1.6a1 1 0 011 1v.2a1 1 0 00.6.9 1 1 0 001.1-.2l.1-.1a1 1 0 011.4 0l1.1 1.1a1 1 0 010 1.4l-.1.1a1 1 0 00-.2 1.1 1 1 0 00.9.6H20a1 1 0 011 1v1.6a1 1 0 01-1 1h-.2a1 1 0 00-.9.6z"],
};
$: paths = iconPaths[name] || iconPaths.dashboard;
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width={strokeWidth}
stroke-linecap="round"
stroke-linejoin="round"
class={className}
aria-hidden="true"
>
{#each paths as d}
<path d={d} />
{/each}
</svg>

View File

@@ -56,7 +56,7 @@
<!-- Top Navigation Bar --> <!-- Top Navigation Bar -->
<TopNavbar /> <TopNavbar />
<!-- Breadcrumbs --> <!-- Breadcrumbs -->
<div class="mt-16"> <div class="mt-16 pt-3">
<Breadcrumbs /> <Breadcrumbs />
</div> </div>

View File

@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<defs>
<linearGradient id="bg" x1="10%" y1="0%" x2="90%" y2="100%">
<stop offset="0%" stop-color="#0EA5E9" />
<stop offset="45%" stop-color="#06B6D4" />
<stop offset="100%" stop-color="#2563EB" />
</linearGradient>
<linearGradient id="stack" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#FFFFFF" />
<stop offset="100%" stop-color="#E2E8F0" />
</linearGradient>
</defs>
<rect x="4" y="4" width="56" height="56" rx="16" fill="url(#bg)" />
<path d="M32 16 14 24l18 8 18-8-18-8Z" fill="url(#stack)" opacity="0.98" />
<path d="m14 33 18 8 18-8" fill="none" stroke="#F8FAFC" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" />
<path d="m14 42 18 8 18-8" fill="none" stroke="#F8FAFC" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" opacity="0.92" />
<path d="M49 14v6M46 17h6" stroke="#F8FAFC" stroke-width="2.2" stroke-linecap="round" />
</svg>

After

Width:  |  Height:  |  Size: 1013 B