style(layout): sidebar stripe + topbar gradient + bell spring + search ring
Sidebar active items: 4px brand left-edge stripe (rounded-r-full) replacing the
border-l-2 + bg shift; section header smaller-caps + brand-200 colour; user-footer
avatar gets shadow-sm + ring-2 ring-white/30.
Topbar '+ New' uses bg-gradient-brand with shadow-sm + scale-1.02 hover. User
avatar trigger gets shadow-sm + ring-2 ring-white. Notification badge gets
gradient-brand fill + ring-2 ring-background + animate-badge-pop spring keyframe
(retriggers on count change via key={unreadCount}). Command search gets shadow-xs
inset + brand focus ring (ring-4 ring-brand/15).
Adds badge-pop keyframes to tailwind config.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -141,12 +141,18 @@ function NavItemLink({
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
href={item.href as any}
|
href={item.href as any}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium transition-colors duration-150',
|
'relative flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium transition-all duration-150',
|
||||||
'text-[#cdcfd6] hover:bg-[#171f35] hover:text-white',
|
'text-[#cdcfd6] hover:bg-[#171f35] hover:text-white',
|
||||||
active && 'border-l-2 border-[#3a7bc8] bg-[#3a7bc810] text-white pl-[10px]',
|
active && 'text-white pl-[14px]',
|
||||||
collapsed && 'justify-center px-2',
|
collapsed && 'justify-center px-2',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
{active && !collapsed && (
|
||||||
|
<span
|
||||||
|
aria-hidden
|
||||||
|
className="absolute left-0 top-1 bottom-1 w-1 rounded-r-full bg-[#3a7bc8]"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<item.icon
|
<item.icon
|
||||||
className={cn(
|
className={cn(
|
||||||
'shrink-0',
|
'shrink-0',
|
||||||
@@ -231,7 +237,7 @@ function SidebarContent({
|
|||||||
<div key={section.title}>
|
<div key={section.title}>
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<div className="flex items-center justify-between px-1 mb-1">
|
<div className="flex items-center justify-between px-1 mb-1">
|
||||||
<span className="text-[#71768a] text-xs font-medium uppercase tracking-wider">
|
<span className="text-[#83aab1] text-[10px] font-semibold uppercase tracking-[0.12em]">
|
||||||
{section.title}
|
{section.title}
|
||||||
</span>
|
</span>
|
||||||
{section.adminRequired && (
|
{section.adminRequired && (
|
||||||
@@ -284,7 +290,7 @@ function SidebarContent({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Avatar className="w-8 h-8 shrink-0">
|
<Avatar className="w-8 h-8 shrink-0 shadow-sm ring-2 ring-white/30">
|
||||||
<AvatarImage src={undefined} />
|
<AvatarImage src={undefined} />
|
||||||
<AvatarFallback className="bg-[#3a7bc8] text-white text-xs font-semibold">
|
<AvatarFallback className="bg-[#3a7bc8] text-white text-xs font-semibold">
|
||||||
{(user?.name ?? 'U').slice(0, 1).toUpperCase()}
|
{(user?.name ?? 'U').slice(0, 1).toUpperCase()}
|
||||||
|
|||||||
@@ -57,7 +57,10 @@ export function Topbar({ ports, user }: TopbarProps) {
|
|||||||
{/* + New dropdown */}
|
{/* + New dropdown */}
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button size="sm" className="bg-brand hover:bg-brand-500 text-white gap-1.5">
|
<Button
|
||||||
|
size="sm"
|
||||||
|
className="bg-gradient-brand hover:opacity-90 text-white gap-1.5 shadow-sm transition-all duration-base ease-smooth hover:scale-[1.02]"
|
||||||
|
>
|
||||||
<Plus className="w-4 h-4" />
|
<Plus className="w-4 h-4" />
|
||||||
<span className="hidden sm:inline">New</span>
|
<span className="hidden sm:inline">New</span>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -93,7 +96,7 @@ export function Topbar({ ports, user }: TopbarProps) {
|
|||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="ghost" size="icon" className="rounded-full">
|
<Button variant="ghost" size="icon" className="rounded-full">
|
||||||
<Avatar className="w-7 h-7">
|
<Avatar className="w-7 h-7 shadow-sm ring-2 ring-white">
|
||||||
<AvatarImage src={undefined} />
|
<AvatarImage src={undefined} />
|
||||||
<AvatarFallback className="bg-brand text-white text-xs font-semibold">
|
<AvatarFallback className="bg-brand text-white text-xs font-semibold">
|
||||||
{(user?.name ?? 'U').slice(0, 1).toUpperCase()}
|
{(user?.name ?? 'U').slice(0, 1).toUpperCase()}
|
||||||
|
|||||||
@@ -57,7 +57,10 @@ export function NotificationBell() {
|
|||||||
<Button variant="ghost" size="icon" className="relative">
|
<Button variant="ghost" size="icon" className="relative">
|
||||||
<Bell className="h-5 w-5" />
|
<Bell className="h-5 w-5" />
|
||||||
{unreadCount > 0 && (
|
{unreadCount > 0 && (
|
||||||
<span className="absolute -top-0.5 -right-0.5 flex h-4 w-4 items-center justify-center rounded-full bg-blue-500 text-[10px] font-bold text-white">
|
<span
|
||||||
|
key={unreadCount}
|
||||||
|
className="absolute -top-0.5 -right-0.5 flex h-4 w-4 items-center justify-center rounded-full bg-gradient-brand text-[10px] font-bold text-white shadow-sm ring-2 ring-background animate-badge-pop"
|
||||||
|
>
|
||||||
{unreadCount > 99 ? '99+' : unreadCount}
|
{unreadCount > 99 ? '99+' : unreadCount}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -84,8 +84,10 @@ export function CommandSearch() {
|
|||||||
{/* ── Single persistent search bar ── */}
|
{/* ── Single persistent search bar ── */}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-2 rounded-md border bg-background px-2.5 transition-all duration-150',
|
'flex items-center gap-2 rounded-md border bg-background px-2.5 shadow-xs transition-all duration-base ease-smooth',
|
||||||
focused ? 'border-muted-foreground/40 w-64 lg:w-80' : 'w-44 lg:w-60',
|
focused
|
||||||
|
? 'border-brand/60 ring-4 ring-brand/15 w-64 lg:w-80'
|
||||||
|
: 'border-input w-44 lg:w-60',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Search className="h-4 w-4 shrink-0 text-muted-foreground" />
|
<Search className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||||
|
|||||||
@@ -163,10 +163,16 @@ export default {
|
|||||||
height: '0',
|
height: '0',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'badge-pop': {
|
||||||
|
'0%': { transform: 'scale(0.5)', opacity: '0' },
|
||||||
|
'60%': { transform: 'scale(1.18)', opacity: '1' },
|
||||||
|
'100%': { transform: 'scale(1)', opacity: '1' },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
animation: {
|
animation: {
|
||||||
'accordion-down': 'accordion-down 0.2s ease-out',
|
'accordion-down': 'accordion-down 0.2s ease-out',
|
||||||
'accordion-up': 'accordion-up 0.2s ease-out',
|
'accordion-up': 'accordion-up 0.2s ease-out',
|
||||||
|
'badge-pop': 'badge-pop 0.32s cubic-bezier(0.34, 1.56, 0.64, 1)',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user