Redesign sidebar with frosted glass effect

Based on Figma design, updated sidebar to feature:
- Frosted glass background with backdrop blur
- Dark gradient app icon (from-[#303035] to-[#26262c])
- Filled icons when active, stroked when inactive
- Smaller, more compact nav items (32x32px)
- Updated sidebar CSS variables for translucent colors
- Added glass utility classes for backdrop blur effects
- User avatar at bottom with gradient background

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
RevIQ
2026-01-08 07:38:23 +08:00
parent d260821964
commit b7e76434e2
2 changed files with 141 additions and 92 deletions

View File

@@ -99,14 +99,16 @@
--chart-4: oklch(0.7 0.16 85); --chart-4: oklch(0.7 0.16 85);
--chart-5: oklch(0.65 0.18 35); --chart-5: oklch(0.65 0.18 35);
--sidebar: oklch(0.98 0.001 280); /* Sidebar - frosted glass style */
--sidebar: oklch(0.93 0 0 / 0.8);
--sidebar-foreground: oklch(0.145 0.005 285); --sidebar-foreground: oklch(0.145 0.005 285);
--sidebar-primary: oklch(0.205 0.006 285); --sidebar-primary: oklch(0.205 0.006 285);
--sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.94 0.002 280); --sidebar-accent: oklch(0 0 0 / 0.1);
--sidebar-accent-foreground: oklch(0.205 0.006 285); --sidebar-accent-foreground: oklch(0.145 0.005 285);
--sidebar-border: oklch(0.91 0.003 280); --sidebar-border: oklch(0 0 0 / 0.05);
--sidebar-ring: oklch(0.65 0.015 280); --sidebar-ring: oklch(0.65 0.015 280);
--sidebar-muted: oklch(0 0 0 / 0.3);
} }
.dark { .dark {
@@ -146,14 +148,16 @@
--chart-4: oklch(0.6 0.2 310); --chart-4: oklch(0.6 0.2 310);
--chart-5: oklch(0.65 0.22 25); --chart-5: oklch(0.65 0.22 25);
--sidebar: oklch(0.14 0.005 280); /* Sidebar - frosted glass style (dark) */
--sidebar: oklch(0.18 0.005 280 / 0.9);
--sidebar-foreground: oklch(0.96 0 0); --sidebar-foreground: oklch(0.96 0 0);
--sidebar-primary: oklch(0.6 0.22 250); --sidebar-primary: oklch(0.6 0.22 250);
--sidebar-primary-foreground: oklch(0.96 0 0); --sidebar-primary-foreground: oklch(0.96 0 0);
--sidebar-accent: oklch(0.22 0.005 280); --sidebar-accent: oklch(1 0 0 / 0.1);
--sidebar-accent-foreground: oklch(0.96 0 0); --sidebar-accent-foreground: oklch(0.96 0 0);
--sidebar-border: oklch(0.26 0.005 280); --sidebar-border: oklch(1 0 0 / 0.05);
--sidebar-ring: oklch(0.5 0.01 280); --sidebar-ring: oklch(0.5 0.01 280);
--sidebar-muted: oklch(1 0 0 / 0.3);
} }
@theme inline { @theme inline {
@@ -198,6 +202,7 @@
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border); --color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring); --color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-muted: var(--sidebar-muted);
} }
@layer base { @layer base {
@@ -236,7 +241,7 @@
} }
} }
/* Utility classes for consistent shadows */ /* Utility classes for consistent shadows and effects */
@layer utilities { @layer utilities {
.shadow-card { .shadow-card {
box-shadow: box-shadow:
@@ -255,4 +260,13 @@
0 4px 6px -1px oklch(0 0 0 / 0.05), 0 4px 6px -1px oklch(0 0 0 / 0.05),
0 2px 4px -2px oklch(0 0 0 / 0.05); 0 2px 4px -2px oklch(0 0 0 / 0.05);
} }
/* Frosted glass effect */
.glass {
@apply backdrop-blur-xl backdrop-saturate-150;
}
.glass-subtle {
@apply backdrop-blur-md backdrop-saturate-125;
}
} }

View File

@@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import { page } from "$app/stores"; import { page } from "$app/stores";
import { Separator } from "$lib/components/ui/separator";
import { cn } from "$lib/utils.js"; import { cn } from "$lib/utils.js";
interface Props { interface Props {
@@ -38,63 +37,90 @@ const bottomItems = [
<aside <aside
class={cn( class={cn(
"flex h-screen w-[72px] flex-col items-center bg-sidebar py-5", "flex h-screen w-[80px] flex-col items-center bg-sidebar glass",
"border-r border-sidebar-border",
className, className,
)} )}
> >
<!-- Logo --> <!-- App Icon -->
<a <div class="flex h-[94px] items-center justify-center">
href="/" <a
aria-label="Home" href="/"
class="group flex h-10 w-10 items-center justify-center rounded-lg bg-foreground transition-all duration-200 hover:scale-[1.02]" aria-label="Home"
> class="group flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-b from-[#303035] to-[#26262c] shadow-sm transition-transform duration-200 hover:scale-105"
<svg
class="h-5 w-5 text-background transition-transform duration-200 group-hover:scale-105"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2.5"
> >
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" stroke-linecap="round" stroke-linejoin="round" /> <svg
</svg> class="h-4 w-4 text-white transition-transform duration-200 group-hover:scale-110"
</a> viewBox="0 0 24 24"
fill="none"
<Separator class="my-5 w-8" /> stroke="currentColor"
stroke-width="2.5"
>
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</a>
</div>
<!-- Main Navigation --> <!-- Main Navigation -->
<nav class="flex flex-1 flex-col items-center gap-1"> <nav class="flex flex-1 flex-col items-center gap-3">
{#each navItems as item} {#each navItems as item}
{@const isActive = $page.url.pathname === item.href || (item.href !== "/" && $page.url.pathname.startsWith(item.href))} {@const isActive =
$page.url.pathname === item.href ||
(item.href !== "/" && $page.url.pathname.startsWith(item.href))}
<a <a
href={item.href} href={item.href}
class={cn( class={cn(
"group relative flex h-10 w-10 items-center justify-center rounded-lg transition-all duration-150", "group relative flex h-8 w-8 items-center justify-center rounded-lg transition-all duration-150",
isActive isActive
? "bg-sidebar-accent text-foreground" ? "bg-sidebar-accent text-sidebar-foreground"
: "text-muted-foreground hover:bg-sidebar-accent/60 hover:text-foreground", : "text-sidebar-muted hover:bg-sidebar-accent/50 hover:text-sidebar-foreground",
)} )}
aria-label={item.label} aria-label={item.label}
aria-current={isActive ? "page" : undefined} aria-current={isActive ? "page" : undefined}
> >
{#if item.icon === "home"} {#if item.icon === "home"}
<svg class="h-[18px] w-[18px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"> {#if isActive}
<path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" stroke-linecap="round" stroke-linejoin="round" /> <svg class="h-4 w-4" viewBox="0 0 24 24" fill="currentColor">
<path d="M9 22V12h6v10" stroke-linecap="round" stroke-linejoin="round" /> <path
</svg> d="M12.97 2.59a1.5 1.5 0 00-1.94 0l-7.5 6.363A1.5 1.5 0 003 10.097V19.5A1.5 1.5 0 004.5 21h4.75a.75.75 0 00.75-.75V14h4v6.25c0 .414.336.75.75.75h4.75a1.5 1.5 0 001.5-1.5v-9.403a1.5 1.5 0 00-.53-1.144l-7.5-6.363z"
/>
</svg>
{:else}
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75">
<path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" stroke-linecap="round" stroke-linejoin="round" />
<path d="M9 22V12h6v10" stroke-linecap="round" stroke-linejoin="round" />
</svg>
{/if}
{:else if item.icon === "chart"} {:else if item.icon === "chart"}
<svg class="h-[18px] w-[18px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"> {#if isActive}
<path d="M18 20V10M12 20V4M6 20v-6" stroke-linecap="round" stroke-linejoin="round" /> <svg class="h-4 w-4" viewBox="0 0 24 24" fill="currentColor">
</svg> <path
d="M18 3a1 1 0 011 1v16a1 1 0 11-2 0V4a1 1 0 011-1zM12 7a1 1 0 011 1v12a1 1 0 11-2 0V8a1 1 0 011-1zM6 11a1 1 0 011 1v8a1 1 0 11-2 0v-8a1 1 0 011-1z"
/>
</svg>
{:else}
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75">
<path d="M18 20V10M12 20V4M6 20v-6" stroke-linecap="round" stroke-linejoin="round" />
</svg>
{/if}
{:else if item.icon === "document"} {:else if item.icon === "document"}
<svg class="h-[18px] w-[18px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"> {#if isActive}
<path <svg class="h-4 w-4" viewBox="0 0 24 24" fill="currentColor">
d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8l-6-6z" <path
stroke-linecap="round" fill-rule="evenodd"
stroke-linejoin="round" d="M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V7.875L14.25 1.5H5.625zM14.25 3v4.5a.75.75 0 00.75.75h4.5L14.25 3zM8.25 12.75a.75.75 0 000 1.5h7.5a.75.75 0 000-1.5h-7.5zm0 3a.75.75 0 000 1.5h7.5a.75.75 0 000-1.5h-7.5z"
/> clip-rule="evenodd"
<path d="M14 2v6h6M16 13H8M16 17H8M10 9H8" stroke-linecap="round" stroke-linejoin="round" /> />
</svg> </svg>
{:else}
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75">
<path
d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8l-6-6z"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path d="M14 2v6h6M16 13H8M16 17H8M10 9H8" stroke-linecap="round" stroke-linejoin="round" />
</svg>
{/if}
{/if} {/if}
<!-- Tooltip --> <!-- Tooltip -->
@@ -103,57 +129,66 @@ const bottomItems = [
> >
{item.label} {item.label}
</span> </span>
<!-- Active indicator -->
{#if isActive}
<span class="absolute -left-[1px] h-5 w-[3px] rounded-r-full bg-foreground"></span>
{/if}
</a> </a>
{/each} {/each}
<!-- Bottom items -->
<div class="mt-auto flex flex-col items-center gap-3">
{#each bottomItems as item}
{@const isActive = $page.url.pathname === item.href}
<a
href={item.href}
class={cn(
"group relative flex h-8 w-8 items-center justify-center rounded-lg transition-all duration-150",
isActive
? "bg-sidebar-accent text-sidebar-foreground"
: "text-sidebar-muted hover:bg-sidebar-accent/50 hover:text-sidebar-foreground",
)}
aria-label={item.label}
aria-current={isActive ? "page" : undefined}
>
{#if item.icon === "settings"}
{#if isActive}
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="currentColor">
<path
fill-rule="evenodd"
d="M11.078 2.25c-.917 0-1.699.663-1.85 1.567L9.05 4.889c-.02.12-.115.26-.297.348a7.493 7.493 0 00-.986.57c-.166.115-.334.126-.45.083L6.3 5.508a1.875 1.875 0 00-2.282.819l-.922 1.597a1.875 1.875 0 00.432 2.385l.84.692c.095.078.17.229.154.43a7.598 7.598 0 000 1.139c.015.2-.059.352-.153.43l-.841.692a1.875 1.875 0 00-.432 2.385l.922 1.597a1.875 1.875 0 002.282.818l1.019-.382c.115-.043.283-.031.45.082.312.214.641.405.985.57.182.088.277.228.297.35l.178 1.071c.151.904.933 1.567 1.85 1.567h1.844c.916 0 1.699-.663 1.85-1.567l.178-1.072c.02-.12.114-.26.297-.349.344-.165.673-.356.985-.57.167-.114.335-.125.45-.082l1.02.382a1.875 1.875 0 002.28-.819l.923-1.597a1.875 1.875 0 00-.432-2.385l-.84-.692c-.095-.078-.17-.229-.154-.43a7.614 7.614 0 000-1.139c-.016-.2.059-.352.153-.43l.84-.692c.708-.582.891-1.59.433-2.385l-.922-1.597a1.875 1.875 0 00-2.282-.818l-1.02.382c-.114.043-.282.031-.449-.083a7.49 7.49 0 00-.985-.57c-.183-.087-.277-.227-.297-.348l-.179-1.072a1.875 1.875 0 00-1.85-1.567h-1.843zM12 15.75a3.75 3.75 0 100-7.5 3.75 3.75 0 000 7.5z"
clip-rule="evenodd"
/>
</svg>
{:else}
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75">
<circle cx="12" cy="12" r="3" />
<path
d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 01-2.83 2.83l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 112.83-2.83l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 114 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 112.83 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
{/if}
{/if}
<span
class="pointer-events-none absolute left-full ml-3 whitespace-nowrap rounded-md bg-foreground px-2.5 py-1.5 text-xs font-medium text-background opacity-0 shadow-lg transition-all duration-150 group-hover:opacity-100"
>
{item.label}
</span>
</a>
{/each}
</div>
</nav> </nav>
<!-- Bottom Navigation --> <!-- User Avatar -->
<div class="flex flex-col items-center gap-1"> <div class="flex h-[80px] items-center justify-center">
{#each bottomItems as item}
{@const isActive = $page.url.pathname === item.href}
<a
href={item.href}
class={cn(
"group relative flex h-10 w-10 items-center justify-center rounded-lg transition-all duration-150",
isActive
? "bg-sidebar-accent text-foreground"
: "text-muted-foreground hover:bg-sidebar-accent/60 hover:text-foreground",
)}
aria-label={item.label}
aria-current={isActive ? "page" : undefined}
>
{#if item.icon === "settings"}
<svg class="h-[18px] w-[18px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75">
<circle cx="12" cy="12" r="3" />
<path
d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 01-2.83 2.83l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 112.83-2.83l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 114 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 112.83 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
{/if}
<span
class="pointer-events-none absolute left-full ml-3 whitespace-nowrap rounded-md bg-foreground px-2.5 py-1.5 text-xs font-medium text-background opacity-0 shadow-lg transition-all duration-150 group-hover:opacity-100"
>
{item.label}
</span>
</a>
{/each}
<Separator class="my-3 w-8" />
<!-- User Avatar -->
<button <button
class="flex h-9 w-9 items-center justify-center rounded-full bg-gradient-to-br from-chart-1 to-chart-2 text-xs font-semibold text-white shadow-sm ring-2 ring-background transition-transform duration-150 hover:scale-105" class="relative h-6 w-6 overflow-hidden rounded-full ring-1 ring-sidebar-border transition-transform duration-150 hover:scale-110"
aria-label="User menu" aria-label="User menu"
> >
JD <div
class="flex h-full w-full items-center justify-center bg-gradient-to-br from-amber-500 to-orange-600 text-[10px] font-semibold text-white"
>
JD
</div>
</button> </button>
</div> </div>
</aside> </aside>