feat: redesign sidebar with icons, collapse toggle, and improved theme
- Add lucide icons to all navigation items - Collapsible sidebar with icon-only mode and tooltips - Narrower sidebar (w-60 vs w-64), compact top bar (h-12 vs h-14) - Better search bar UX in top bar with keyboard shortcut hint - Refined dark theme with better contrast and separation - Custom thin scrollbar styling - Backdrop blur for sidebar and top bar Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7dbd2c1414
commit
9a33cef951
|
|
@ -21,11 +21,11 @@ export default function AdminLayout({ children }: { children: React.ReactNode })
|
||||||
if (!ready) return null;
|
if (!ready) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen">
|
<div className="flex h-screen bg-background">
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
<div className="flex-1 flex flex-col overflow-hidden">
|
<div className="flex-1 flex flex-col overflow-hidden min-w-0">
|
||||||
<TopBar />
|
<TopBar />
|
||||||
<main className="flex-1 overflow-y-auto p-6">
|
<main className="flex-1 overflow-y-auto scrollbar-thin p-5">
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,58 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState, createContext, useContext } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname } from 'next/navigation';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import {
|
||||||
|
LayoutDashboard,
|
||||||
|
Bot,
|
||||||
|
BookOpen,
|
||||||
|
ClipboardList,
|
||||||
|
Server,
|
||||||
|
Activity,
|
||||||
|
Terminal,
|
||||||
|
Shield,
|
||||||
|
FileSearch,
|
||||||
|
MessageSquare,
|
||||||
|
Building2,
|
||||||
|
Settings,
|
||||||
|
ChevronRight,
|
||||||
|
PanelLeftClose,
|
||||||
|
PanelLeft,
|
||||||
|
} from 'lucide-react';
|
||||||
|
|
||||||
|
/* ---------- Sidebar context for collapse state ---------- */
|
||||||
|
|
||||||
|
interface SidebarContextValue {
|
||||||
|
collapsed: boolean;
|
||||||
|
setCollapsed: (v: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SidebarContext = createContext<SidebarContextValue>({
|
||||||
|
collapsed: false,
|
||||||
|
setCollapsed: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useSidebar = () => useContext(SidebarContext);
|
||||||
|
|
||||||
|
/* ---------- Nav config ---------- */
|
||||||
|
|
||||||
interface NavItem {
|
interface NavItem {
|
||||||
label: string;
|
label: string;
|
||||||
href: string;
|
href: string;
|
||||||
|
icon: React.ReactNode;
|
||||||
children?: { label: string; href: string }[];
|
children?: { label: string; href: string }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const iconClass = 'h-4 w-4 shrink-0';
|
||||||
|
|
||||||
const navItems: NavItem[] = [
|
const navItems: NavItem[] = [
|
||||||
{ label: 'Dashboard', href: '/dashboard' },
|
{ label: 'Dashboard', href: '/dashboard', icon: <LayoutDashboard className={iconClass} /> },
|
||||||
{
|
{
|
||||||
label: 'Agent Config',
|
label: 'Agent Config',
|
||||||
href: '/agent-config',
|
href: '/agent-config',
|
||||||
|
icon: <Bot className={iconClass} />,
|
||||||
children: [
|
children: [
|
||||||
{ label: 'Engine & Prompt', href: '/agent-config' },
|
{ label: 'Engine & Prompt', href: '/agent-config' },
|
||||||
{ label: 'SDK Config', href: '/agent-config/sdk' },
|
{ label: 'SDK Config', href: '/agent-config/sdk' },
|
||||||
|
|
@ -23,11 +60,12 @@ const navItems: NavItem[] = [
|
||||||
{ label: 'Hooks', href: '/agent-config/hooks' },
|
{ label: 'Hooks', href: '/agent-config/hooks' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ label: 'Runbooks', href: '/runbooks' },
|
{ label: 'Runbooks', href: '/runbooks', icon: <BookOpen className={iconClass} /> },
|
||||||
{ label: 'Standing Orders', href: '/standing-orders' },
|
{ label: 'Standing Orders', href: '/standing-orders', icon: <ClipboardList className={iconClass} /> },
|
||||||
{
|
{
|
||||||
label: 'Servers',
|
label: 'Servers',
|
||||||
href: '/servers',
|
href: '/servers',
|
||||||
|
icon: <Server className={iconClass} />,
|
||||||
children: [
|
children: [
|
||||||
{ label: 'All Servers', href: '/servers' },
|
{ label: 'All Servers', href: '/servers' },
|
||||||
{ label: 'Clusters', href: '/servers/clusters' },
|
{ label: 'Clusters', href: '/servers/clusters' },
|
||||||
|
|
@ -36,15 +74,17 @@ const navItems: NavItem[] = [
|
||||||
{
|
{
|
||||||
label: 'Monitoring',
|
label: 'Monitoring',
|
||||||
href: '/monitoring/alert-rules',
|
href: '/monitoring/alert-rules',
|
||||||
|
icon: <Activity className={iconClass} />,
|
||||||
children: [
|
children: [
|
||||||
{ label: 'Alert Rules', href: '/monitoring/alert-rules' },
|
{ label: 'Alert Rules', href: '/monitoring/alert-rules' },
|
||||||
{ label: 'Health Checks', href: '/monitoring/health-checks' },
|
{ label: 'Health Checks', href: '/monitoring/health-checks' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ label: 'Terminal', href: '/terminal' },
|
{ label: 'Terminal', href: '/terminal', icon: <Terminal className={iconClass} /> },
|
||||||
{
|
{
|
||||||
label: 'Security',
|
label: 'Security',
|
||||||
href: '/security/risk-rules',
|
href: '/security/risk-rules',
|
||||||
|
icon: <Shield className={iconClass} />,
|
||||||
children: [
|
children: [
|
||||||
{ label: 'Risk Rules', href: '/security/risk-rules' },
|
{ label: 'Risk Rules', href: '/security/risk-rules' },
|
||||||
{ label: 'Credentials', href: '/security/credentials' },
|
{ label: 'Credentials', href: '/security/credentials' },
|
||||||
|
|
@ -53,16 +93,19 @@ const navItems: NavItem[] = [
|
||||||
{
|
{
|
||||||
label: 'Audit',
|
label: 'Audit',
|
||||||
href: '/audit/logs',
|
href: '/audit/logs',
|
||||||
|
icon: <FileSearch className={iconClass} />,
|
||||||
children: [
|
children: [
|
||||||
{ label: 'Logs', href: '/audit/logs' },
|
{ label: 'Logs', href: '/audit/logs' },
|
||||||
{ label: 'Session Replay', href: '/audit/replay' },
|
{ label: 'Session Replay', href: '/audit/replay' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ label: 'Communication', href: '/communication' },
|
{ label: 'Communication', href: '/communication', icon: <MessageSquare className={iconClass} /> },
|
||||||
{ label: 'Tenants', href: '/tenants' },
|
{ label: 'Tenants', href: '/tenants', icon: <Building2 className={iconClass} /> },
|
||||||
{ label: 'Settings', href: '/settings' },
|
{ label: 'Settings', href: '/settings', icon: <Settings className={iconClass} /> },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/* ---------- Helpers ---------- */
|
||||||
|
|
||||||
function isActive(pathname: string, href: string) {
|
function isActive(pathname: string, href: string) {
|
||||||
return pathname === href || pathname.startsWith(href + '/');
|
return pathname === href || pathname.startsWith(href + '/');
|
||||||
}
|
}
|
||||||
|
|
@ -72,89 +115,170 @@ function isGroupActive(pathname: string, item: NavItem) {
|
||||||
return item.children?.some((c) => isActive(pathname, c.href)) ?? false;
|
return item.children?.some((c) => isActive(pathname, c.href)) ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- Tooltip for collapsed mode ---------- */
|
||||||
|
|
||||||
|
function Tooltip({ children, label }: { children: React.ReactNode; label: string }) {
|
||||||
|
return (
|
||||||
|
<div className="relative group/tip">
|
||||||
|
{children}
|
||||||
|
<div
|
||||||
|
className="absolute left-full top-1/2 -translate-y-1/2 ml-2 px-2.5 py-1 rounded-md
|
||||||
|
bg-popover text-popover-foreground text-xs font-medium whitespace-nowrap
|
||||||
|
shadow-md border opacity-0 pointer-events-none
|
||||||
|
group-hover/tip:opacity-100 transition-opacity duration-150 z-50"
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Main component ---------- */
|
||||||
|
|
||||||
export function Sidebar() {
|
export function Sidebar() {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const [expanded, setExpanded] = useState<Record<string, boolean>>({});
|
const [expanded, setExpanded] = useState<Record<string, boolean>>({});
|
||||||
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
|
|
||||||
const toggle = (label: string) =>
|
const toggle = (label: string) =>
|
||||||
setExpanded((prev) => ({ ...prev, [label]: !prev[label] }));
|
setExpanded((prev) => ({ ...prev, [label]: !prev[label] }));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="w-64 bg-card border-r flex flex-col">
|
<SidebarContext.Provider value={{ collapsed, setCollapsed }}>
|
||||||
<div className="p-4 border-b">
|
<aside
|
||||||
<h1 className="text-xl font-bold">IT0 Admin</h1>
|
className={cn(
|
||||||
<p className="text-xs text-muted-foreground">Operations Console</p>
|
'bg-card/50 backdrop-blur-sm border-r flex flex-col transition-all duration-200 ease-in-out',
|
||||||
</div>
|
collapsed ? 'w-16' : 'w-60',
|
||||||
<nav className="flex-1 p-2 space-y-0.5 overflow-y-auto">
|
)}
|
||||||
{navItems.map((item) => {
|
>
|
||||||
const hasChildren = item.children && item.children.length > 0;
|
{/* Logo area */}
|
||||||
const groupActive = isGroupActive(pathname, item);
|
<div className={cn('border-b flex items-center', collapsed ? 'px-3 py-4 justify-center' : 'px-4 py-4')}>
|
||||||
const isOpen = expanded[item.label] ?? groupActive;
|
<div className="flex items-center gap-2.5 min-w-0">
|
||||||
|
<div className="w-8 h-8 rounded-lg bg-primary/15 flex items-center justify-center shrink-0">
|
||||||
if (!hasChildren) {
|
<span className="text-primary font-bold text-sm">IT</span>
|
||||||
return (
|
|
||||||
<Link
|
|
||||||
key={item.href}
|
|
||||||
href={item.href}
|
|
||||||
className={cn(
|
|
||||||
'flex items-center gap-3 px-3 py-2 rounded-md text-sm transition-colors',
|
|
||||||
isActive(pathname, item.href)
|
|
||||||
? 'bg-primary/10 text-primary'
|
|
||||||
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span>{item.label}</span>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={item.label}>
|
|
||||||
<button
|
|
||||||
onClick={() => toggle(item.label)}
|
|
||||||
className={cn(
|
|
||||||
'flex items-center justify-between w-full px-3 py-2 rounded-md text-sm transition-colors',
|
|
||||||
groupActive
|
|
||||||
? 'text-primary'
|
|
||||||
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span>{item.label}</span>
|
|
||||||
<svg
|
|
||||||
className={cn(
|
|
||||||
'w-4 h-4 transition-transform',
|
|
||||||
isOpen && 'rotate-90',
|
|
||||||
)}
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth={2}
|
|
||||||
>
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
{isOpen && (
|
|
||||||
<div className="ml-4 mt-0.5 space-y-0.5">
|
|
||||||
{item.children!.map((child) => (
|
|
||||||
<Link
|
|
||||||
key={child.href}
|
|
||||||
href={child.href}
|
|
||||||
className={cn(
|
|
||||||
'flex items-center gap-2 px-3 py-1.5 rounded-md text-xs transition-colors',
|
|
||||||
isActive(pathname, child.href)
|
|
||||||
? 'bg-primary/10 text-primary font-medium'
|
|
||||||
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span>{child.label}</span>
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
{!collapsed && (
|
||||||
})}
|
<div className="min-w-0">
|
||||||
</nav>
|
<h1 className="text-sm font-semibold tracking-tight truncate">IT0 Admin</h1>
|
||||||
</aside>
|
<p className="text-[10px] text-muted-foreground leading-none mt-0.5">Operations Console</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Navigation */}
|
||||||
|
<nav className="flex-1 py-2 px-2 space-y-0.5 overflow-y-auto overflow-x-hidden">
|
||||||
|
{navItems.map((item) => {
|
||||||
|
const hasChildren = item.children && item.children.length > 0;
|
||||||
|
const groupActive = isGroupActive(pathname, item);
|
||||||
|
const isOpen = expanded[item.label] ?? groupActive;
|
||||||
|
|
||||||
|
/* ---- Collapsed: icon-only with tooltip ---- */
|
||||||
|
if (collapsed) {
|
||||||
|
const linkTarget = hasChildren
|
||||||
|
? item.children![0].href
|
||||||
|
: item.href;
|
||||||
|
return (
|
||||||
|
<Tooltip key={item.label} label={item.label}>
|
||||||
|
<Link
|
||||||
|
href={linkTarget}
|
||||||
|
className={cn(
|
||||||
|
'flex items-center justify-center h-9 w-full rounded-md transition-colors',
|
||||||
|
groupActive
|
||||||
|
? 'bg-primary/10 text-primary'
|
||||||
|
: 'text-muted-foreground hover:bg-accent hover:text-foreground',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{item.icon}
|
||||||
|
</Link>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Expanded: no children ---- */
|
||||||
|
if (!hasChildren) {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
key={item.href}
|
||||||
|
href={item.href}
|
||||||
|
className={cn(
|
||||||
|
'flex items-center gap-2.5 px-2.5 py-2 rounded-md text-[13px] transition-colors',
|
||||||
|
isActive(pathname, item.href)
|
||||||
|
? 'bg-primary/10 text-primary font-medium'
|
||||||
|
: 'text-muted-foreground hover:bg-accent hover:text-foreground',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{item.icon}
|
||||||
|
<span className="truncate">{item.label}</span>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Expanded: with children ---- */
|
||||||
|
return (
|
||||||
|
<div key={item.label}>
|
||||||
|
<button
|
||||||
|
onClick={() => toggle(item.label)}
|
||||||
|
className={cn(
|
||||||
|
'flex items-center gap-2.5 w-full px-2.5 py-2 rounded-md text-[13px] transition-colors',
|
||||||
|
groupActive
|
||||||
|
? 'text-primary'
|
||||||
|
: 'text-muted-foreground hover:bg-accent hover:text-foreground',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{item.icon}
|
||||||
|
<span className="flex-1 text-left truncate">{item.label}</span>
|
||||||
|
<ChevronRight
|
||||||
|
className={cn(
|
||||||
|
'h-3.5 w-3.5 shrink-0 transition-transform duration-200',
|
||||||
|
isOpen && 'rotate-90',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
{isOpen && (
|
||||||
|
<div className="ml-[18px] mt-0.5 space-y-0.5 border-l border-border/50 pl-2.5">
|
||||||
|
{item.children!.map((child) => (
|
||||||
|
<Link
|
||||||
|
key={child.href}
|
||||||
|
href={child.href}
|
||||||
|
className={cn(
|
||||||
|
'flex items-center px-2.5 py-1.5 rounded-md text-xs transition-colors',
|
||||||
|
isActive(pathname, child.href)
|
||||||
|
? 'bg-primary/10 text-primary font-medium'
|
||||||
|
: 'text-muted-foreground hover:bg-accent hover:text-foreground',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className="truncate">{child.label}</span>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Collapse toggle at bottom */}
|
||||||
|
<div className="border-t p-2">
|
||||||
|
<button
|
||||||
|
onClick={() => setCollapsed(!collapsed)}
|
||||||
|
className={cn(
|
||||||
|
'flex items-center gap-2.5 w-full rounded-md text-xs text-muted-foreground',
|
||||||
|
'hover:bg-accent hover:text-foreground transition-colors',
|
||||||
|
collapsed ? 'justify-center h-9' : 'px-2.5 py-2',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{collapsed ? (
|
||||||
|
<PanelLeft className="h-4 w-4 shrink-0" />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<PanelLeftClose className="h-4 w-4 shrink-0" />
|
||||||
|
<span>Collapse</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</SidebarContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,49 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useTenantStore } from '@/stores/zustand/tenant-store';
|
import { useTenantStore } from '@/stores/zustand/tenant-store';
|
||||||
|
import { Search, Bell } from 'lucide-react';
|
||||||
|
|
||||||
export function TopBar() {
|
export function TopBar() {
|
||||||
const { currentTenant } = useTenantStore();
|
const { currentTenant } = useTenantStore();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="h-14 border-b bg-card flex items-center justify-between px-6">
|
<header className="h-12 border-b bg-card/50 backdrop-blur-sm flex items-center justify-between px-4 shrink-0">
|
||||||
<div className="flex items-center gap-4">
|
{/* Left: search / command palette */}
|
||||||
<kbd className="px-2 py-1 text-xs bg-muted rounded border text-muted-foreground">
|
<button
|
||||||
Cmd+K
|
className="flex items-center gap-2 px-3 py-1.5 rounded-md border border-border/60
|
||||||
|
text-muted-foreground hover:text-foreground hover:border-border
|
||||||
|
transition-colors text-xs w-56"
|
||||||
|
onClick={() => {
|
||||||
|
/* TODO: open command palette */
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Search className="h-3.5 w-3.5 shrink-0" />
|
||||||
|
<span className="flex-1 text-left">Search...</span>
|
||||||
|
<kbd className="text-[10px] bg-muted/60 px-1.5 py-0.5 rounded border border-border/40 font-mono">
|
||||||
|
⌘K
|
||||||
</kbd>
|
</kbd>
|
||||||
<span className="text-sm text-muted-foreground">Command Palette</span>
|
</button>
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-4">
|
{/* Right: tenant + user */}
|
||||||
<div className="text-sm">
|
<div className="flex items-center gap-3">
|
||||||
|
{/* Notification bell */}
|
||||||
|
<button className="relative p-1.5 rounded-md text-muted-foreground hover:text-foreground hover:bg-accent transition-colors">
|
||||||
|
<Bell className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Divider */}
|
||||||
|
<div className="h-5 w-px bg-border" />
|
||||||
|
|
||||||
|
{/* Tenant indicator */}
|
||||||
|
<div className="text-xs">
|
||||||
<span className="text-muted-foreground">Tenant: </span>
|
<span className="text-muted-foreground">Tenant: </span>
|
||||||
<span className="font-medium">{currentTenant?.name || 'Not selected'}</span>
|
<span className="font-medium">{currentTenant?.name || 'Not selected'}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-8 h-8 rounded-full bg-primary/20 flex items-center justify-center text-sm font-medium">
|
|
||||||
|
{/* User avatar */}
|
||||||
|
<button className="w-7 h-7 rounded-full bg-primary/15 border border-primary/20 flex items-center justify-center text-xs font-semibold text-primary hover:bg-primary/25 transition-colors">
|
||||||
A
|
A
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -4,23 +4,25 @@
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
--background: 222.2 84% 4.9%;
|
--background: 224 71% 4%;
|
||||||
--foreground: 210 40% 98%;
|
--foreground: 213 31% 91%;
|
||||||
--card: 222.2 84% 4.9%;
|
--card: 224 71% 4%;
|
||||||
--card-foreground: 210 40% 98%;
|
--card-foreground: 213 31% 91%;
|
||||||
--primary: 217.2 91.2% 59.8%;
|
--popover: 224 71% 4%;
|
||||||
--primary-foreground: 222.2 47.4% 11.2%;
|
--popover-foreground: 213 31% 91%;
|
||||||
--secondary: 217.2 32.6% 17.5%;
|
--primary: 210 100% 52%;
|
||||||
--secondary-foreground: 210 40% 98%;
|
--primary-foreground: 0 0% 100%;
|
||||||
--muted: 217.2 32.6% 17.5%;
|
--secondary: 222 47% 11%;
|
||||||
--muted-foreground: 215 20.2% 65.1%;
|
--secondary-foreground: 213 31% 91%;
|
||||||
--accent: 217.2 32.6% 17.5%;
|
--muted: 223 47% 11%;
|
||||||
|
--muted-foreground: 215.4 16.3% 56.9%;
|
||||||
|
--accent: 216 34% 17%;
|
||||||
--accent-foreground: 210 40% 98%;
|
--accent-foreground: 210 40% 98%;
|
||||||
--destructive: 0 62.8% 30.6%;
|
--destructive: 0 63% 31%;
|
||||||
--destructive-foreground: 210 40% 98%;
|
--destructive-foreground: 210 40% 98%;
|
||||||
--border: 217.2 32.6% 17.5%;
|
--border: 216 34% 17%;
|
||||||
--input: 217.2 32.6% 17.5%;
|
--input: 216 34% 17%;
|
||||||
--ring: 224.3 76.3% 48%;
|
--ring: 210 100% 52%;
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -30,6 +32,27 @@
|
||||||
@apply border-border;
|
@apply border-border;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground antialiased;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom scrollbar */
|
||||||
|
@layer utilities {
|
||||||
|
.scrollbar-thin {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: hsl(var(--border)) transparent;
|
||||||
|
}
|
||||||
|
.scrollbar-thin::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
.scrollbar-thin::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.scrollbar-thin::-webkit-scrollbar-thumb {
|
||||||
|
background-color: hsl(var(--border));
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: hsl(var(--muted-foreground) / 0.5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,10 @@ const config: Config = {
|
||||||
DEFAULT: 'hsl(var(--card))',
|
DEFAULT: 'hsl(var(--card))',
|
||||||
foreground: 'hsl(var(--card-foreground))',
|
foreground: 'hsl(var(--card-foreground))',
|
||||||
},
|
},
|
||||||
|
popover: {
|
||||||
|
DEFAULT: 'hsl(var(--popover))',
|
||||||
|
foreground: 'hsl(var(--popover-foreground))',
|
||||||
|
},
|
||||||
border: 'hsl(var(--border))',
|
border: 'hsl(var(--border))',
|
||||||
input: 'hsl(var(--input))',
|
input: 'hsl(var(--input))',
|
||||||
ring: 'hsl(var(--ring))',
|
ring: 'hsl(var(--ring))',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue