feature/logo-nav

This commit is contained in:
hreyes
2026-02-09 11:51:28 -06:00
parent 33a53d95b4
commit 6394a831df
2 changed files with 115 additions and 41 deletions

View File

@@ -9,59 +9,91 @@
import { getBackendAssetUrl } from "$lib/utils";
const sidebar = useSidebar();
// Derivar la URL del logo usando el endpoint específico
let activeCompanyLogoUrl = $derived(
companyStore.activeCompany?.logo
? getBackendAssetUrl(`v1/a76/company/${companyStore.activeCompany.id}/logo/image?t=${new Date().getTime()}`)
companyStore.activeCompany?.logo
? getBackendAssetUrl(
`v1/a76/company/${companyStore.activeCompany.id}/logo/image?t=${Date.now()}`
)
: null
);
// Iniciales de la compañía activa (2 primeras letras)
let activeCompanyInitials = $derived(
companyStore.activeCompany?.name?.slice(0, 2).toUpperCase() || 'CO'
companyStore.activeCompany?.name?.slice(0, 2).toUpperCase() || "CO"
);
// Fallback en degradé cuando no hay logo cargado
const fallbackBg =
"radial-gradient(circle at 30% 30%, rgba(0,0,0,0.08), rgba(0,0,0,0.12)), linear-gradient(135deg, rgba(99,102,241,0.12), rgba(14,165,233,0.18))";
const logoBg = $derived(
activeCompanyLogoUrl
? `url(${activeCompanyLogoUrl})`
: fallbackBg
);
</script>
<Sidebar.Menu>
<Sidebar.MenuItem>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Sidebar.MenuButton
{...props}
size="lg"
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<div
class="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg overflow-hidden"
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Sidebar.MenuButton
{...props}
size="lg"
class="relative overflow-hidden data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground group-has-data-[state=collapsed]/sidebar-wrapper:aspect-square group-has-data-[state=collapsed]/sidebar-wrapper:h-10 group-has-data-[state=collapsed]/sidebar-wrapper:w-10 group-has-data-[state=collapsed]/sidebar-wrapper:rounded-lg group-has-data-[state=collapsed]/sidebar-wrapper:justify-center group-has-data-[state=collapsed]/sidebar-wrapper:gap-0"
>
{#if activeCompanyLogoUrl}
<img
src={activeCompanyLogoUrl}
alt={companyStore.activeCompany?.name || 'Company'}
class="size-full object-cover"
onerror={(e) => { (e.currentTarget as HTMLImageElement).style.display = 'none'; }}
/>
{:else}
<span class="text-sm font-semibold text-white">{activeCompanyInitials}</span>
{/if}
</div>
<div class="grid flex-1 text-left text-sm leading-tight min-w-0">
<span class="truncate font-medium">
{companyStore.activeCompany?.name || 'Seleccionar compañía'}
</span>
{#if companyStore.activeCompany?.rfc}
<span class="truncate text-xs text-muted-foreground">
{companyStore.activeCompany.rfc}
<!-- Logo skin visible cuando el sidebar está colapsado (icon mode) -->
<span
aria-hidden
class="pointer-events-none absolute inset-0 bg-center bg-contain bg-no-repeat transition-all duration-300 opacity-0 group-has-data-[state=collapsed]/sidebar-wrapper:opacity-90 group-has-data-[state=collapsed]/sidebar-wrapper:blur-[0.6px] group-has-data-[state=collapsed]/sidebar-wrapper:scale-100"
style:background-image={logoBg}
/>
<!-- Overlay para legibilidad del ícono/chevron -->
<span
aria-hidden
class="pointer-events-none absolute inset-0 transition-opacity duration-300 opacity-0 group-has-data-[state=collapsed]/sidebar-wrapper:bg-black/12 group-has-data-[state=collapsed]/sidebar-wrapper:opacity-100"
/>
<div
class="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg overflow-hidden group-has-data-[state=collapsed]/sidebar-wrapper:hidden"
>
{#if activeCompanyLogoUrl}
<img
src={activeCompanyLogoUrl}
alt={companyStore.activeCompany?.name || "Company"}
class="size-full object-cover"
onerror={(e) => {
(e.currentTarget as HTMLImageElement).style.display = "none";
}}
/>
{:else}
<span class="text-sm font-semibold text-white">{activeCompanyInitials}</span>
{/if}
</div>
<div class="grid flex-1 text-left text-sm leading-tight min-w-0 group-has-data-[state=collapsed]/sidebar-wrapper:hidden">
<span class="truncate font-medium">
{companyStore.activeCompany?.name || "Seleccionar compañía"}
</span>
{/if}
</div>
<ChevronsUpDownIcon class="ml-auto size-4" />
</Sidebar.MenuButton>
{/snippet}
</DropdownMenu.Trigger>
{#if companyStore.activeCompany?.rfc}
<span class="truncate text-xs text-muted-foreground">
{companyStore.activeCompany.rfc}
</span>
{/if}
</div>
<ChevronsUpDownIcon class="ml-auto size-4 group-has-data-[state=collapsed]/sidebar-wrapper:hidden" />
<!-- Isotipo compacto visible solo en modo colapsado -->
<div
class="relative z-10 hidden size-8 items-center justify-center rounded-md bg-sidebar-primary text-sidebar-foreground text-sm font-semibold shadow-sm ring-1 ring-sidebar-border/40 group-has-data-[state=collapsed]/sidebar-wrapper:flex"
>
{activeCompanyInitials}
</div>
</Sidebar.MenuButton>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content
class="w-(--bits-dropdown-menu-anchor-width) min-w-56 rounded-lg"
align="start"

View File

@@ -1,8 +1,9 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button/index.js";
import { cn } from "$lib/utils.js";
import { cn, getBackendAssetUrl } from "$lib/utils.js";
import PanelLeftIcon from "@lucide/svelte/icons/panel-left";
import type { ComponentProps } from "svelte";
import { companyStore } from "$lib/stores/company.svelte";
import { useSidebar } from "./context.svelte.js";
let {
@@ -15,6 +16,19 @@
} = $props();
const sidebar = useSidebar();
// Derive the current company logo as a skin for the trigger when collapsed.
let activeCompanyLogoUrl = $derived(
companyStore.activeCompany?.logo
? getBackendAssetUrl(
`v1/a76/company/${companyStore.activeCompany.id}/logo/image?t=${Date.now()}`
)
: null
);
// Keep a short fallback in case there is no logo.
const fallbackBg =
"radial-gradient(circle at 30% 30%, rgba(0,0,0,0.08), rgba(0,0,0,0.12)), linear-gradient(135deg, rgba(99,102,241,0.08), rgba(14,165,233,0.14))";
</script>
<Button
@@ -22,7 +36,13 @@
data-slot="sidebar-trigger"
variant="ghost"
size="icon"
class={cn("size-7", className)}
class={cn(
"relative flex aspect-square h-10 w-10 items-center justify-center overflow-hidden rounded-lg",
"border border-sidebar-border/60 bg-sidebar shadow-sm transition-all duration-300",
"hover:bg-sidebar-accent/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60",
"group-data-[collapsible=icon]/sidebar-wrapper:size-9",
className
)}
type="button"
onclick={(e) => {
onclick?.(e);
@@ -30,6 +50,28 @@
}}
{...restProps}
>
<PanelLeftIcon />
<!-- Logo skin behind the icon; fades/blurs when collapsed for legibility -->
<span
aria-hidden
class={cn(
"absolute inset-0 bg-center bg-no-repeat transition-all duration-300",
"bg-contain",
sidebar.state === "collapsed"
? "opacity-75 blur-[0.5px] scale-95"
: "opacity-100 blur-0 scale-105"
)}
style:background-image={activeCompanyLogoUrl ? `url(${activeCompanyLogoUrl})` : fallbackBg}
/>
<!-- Subtle overlay to keep the icon readable over the logo skin -->
<span
class={cn(
"pointer-events-none absolute inset-0 transition-opacity duration-300",
sidebar.state === "collapsed" ? "bg-black/10 opacity-100" : "opacity-0"
)}
aria-hidden
/>
<PanelLeftIcon class="relative z-10 transition-transform duration-300" />
<span class="sr-only">Toggle Sidebar</span>
</Button>