Redesign frontend UI with premium SaaS styling and app icon

- Restyle design system: refined color palette, 16px base font, antialiased
  text rendering, improved typography hierarchy across all pages
- Update base components (button, input, card, checkbox, dialog, sidebar)
  with modern rounded corners, subtle shadows, and smooth transitions
- Redesign layout: remove header bar, move controls to sidebar footer,
  add two-column todo dashboard with stats and upcoming reminders
- Replace hardcoded slate colors with design token system throughout
- Add app icon (favicon, apple-icon, sidebar logo) from notify_icon.png
- Improve typography: page titles 20px, section titles 18px, sidebar
  nav 14px, stats 24px semibold, body text with proper line-height

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Michael Dong
2026-02-11 15:19:14 +08:00
parent f34c01afdf
commit 8131ec7af2
25 changed files with 489 additions and 316 deletions

View File

@@ -2,38 +2,18 @@
import type { ReactNode } from "react";
import { Button } from "@/components/ui/button";
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
import AppSidebar from "@/components/AppSidebar";
import LanguageSwitcher from "@/components/LanguageSwitcher";
import { clearToken } from "@/lib/auth";
import { useTranslation } from "@/lib/i18n";
import { NotificationProvider } from "@/lib/notification-context";
import { UserProvider } from "@/lib/user-context";
const AppShellContent = ({ children }: { children: ReactNode }) => {
const t = useTranslation();
return (
<SidebarProvider defaultOpen>
<div className="flex min-h-screen bg-slate-100 p-6">
<div className="flex min-h-screen bg-muted/50 p-6">
<AppSidebar />
<SidebarInset>
<div className="mx-auto flex w-full max-w-6xl flex-col gap-6 px-6">
<div className="rounded-xl bg-white p-4 shadow-sm">
<div className="flex flex-wrap items-center justify-between gap-4">
<div>
<h1 className="text-xl font-semibold leading-tight text-slate-900">Notify</h1>
<p className="text-sm text-slate-500">{t("appDesc")}</p>
</div>
<div className="flex items-center gap-3">
<LanguageSwitcher />
<Button variant="outline" onClick={() => clearToken()}>
{t("logout")}
</Button>
</div>
</div>
</div>
<div className="w-full max-w-[1200px] py-6 px-6">
<div className="grid gap-5">{children}</div>
</div>
</SidebarInset>

View File

@@ -1,8 +1,9 @@
"use client";
import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { Bell, BellDot, ListTodo, Settings, UserPlus } from "lucide-react";
import { Bell, BellDot, ListTodo, LogOut, Settings, UserPlus } from "lucide-react";
import {
Sidebar,
@@ -10,7 +11,6 @@ import {
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
@@ -18,9 +18,11 @@ import {
SidebarSeparator,
} from "@/components/ui/sidebar";
import Avatar from "@/components/ui/avatar";
import LanguageSwitcher from "@/components/LanguageSwitcher";
import { useTranslation, type TranslationKey } from "@/lib/i18n";
import { useNotification } from "@/lib/notification-context";
import { useUser } from "@/lib/user-context";
import { clearToken } from "@/lib/auth";
const navItems: { href: string; labelKey: TranslationKey; icon: typeof ListTodo }[] = [
{ href: "/todos", labelKey: "navTodo", icon: ListTodo },
@@ -40,19 +42,14 @@ const AppSidebar = () => {
<Sidebar variant="inset" className="h-[calc(100vh-3rem)] self-start bg-white">
<SidebarHeader className="gap-2 px-3 py-4">
<div className="flex items-center gap-2">
<span className="flex h-8 w-8 items-center justify-center rounded-full bg-blue-500/10 text-blue-600">
<span className="text-base font-semibold"></span>
</span>
<span className="text-base font-semibold group-data-[state=collapsed]/sidebar:hidden">
<Image src="/notify_icon.png" alt="Notify" width={28} height={28} className="h-7 w-7 rounded-lg" />
<span className="text-[15px] font-semibold tracking-tight text-foreground group-data-[state=collapsed]/sidebar:hidden">
notify
</span>
</div>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel className="group-data-[state=collapsed]/sidebar:hidden">
{t("navigation")}
</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{navItems.map((item) => {
@@ -67,7 +64,7 @@ const AppSidebar = () => {
>
<Link href={item.href}>
<span className="relative">
<item.icon className="h-4 w-4" />
<item.icon className={`h-[18px] w-[18px] ${isActive ? "text-sidebar-primary" : "text-muted-foreground"}`} />
{isNotifications && unreadCount > 0 && (
<span className="absolute -right-1 -top-1 hidden h-2 w-2 rounded-full bg-red-500 group-data-[state=collapsed]/sidebar:inline-flex" />
)}
@@ -90,12 +87,25 @@ const AppSidebar = () => {
</SidebarGroup>
</SidebarContent>
<SidebarSeparator />
<SidebarFooter className="px-3 py-4">
<div className="flex items-center gap-2 group-data-[state=collapsed]/sidebar:justify-center">
<Avatar username={user?.username} src={user?.avatar} size="sm" />
<span className="text-sm font-medium text-slate-700 group-data-[state=collapsed]/sidebar:hidden">
{user?.username}
</span>
<SidebarFooter className="px-3 py-3">
<div className="flex items-center justify-between group-data-[state=collapsed]/sidebar:justify-center">
<div className="flex items-center gap-2 min-w-0">
<Avatar username={user?.username} src={user?.avatar} size="sm" />
<span className="truncate text-sm font-medium text-foreground/80 group-data-[state=collapsed]/sidebar:hidden">
{user?.username}
</span>
</div>
<div className="flex items-center gap-1 group-data-[state=collapsed]/sidebar:hidden">
<LanguageSwitcher />
<button
type="button"
onClick={() => clearToken()}
className="inline-flex h-8 w-8 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
title={t("logout")}
>
<LogOut className="h-4 w-4" />
</button>
</div>
</div>
</SidebarFooter>
</Sidebar>

View File

@@ -3,7 +3,6 @@
import { useState } from "react";
import { Globe } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Popover,
PopoverContent,
@@ -17,28 +16,28 @@ const languages: { value: Locale; label: string }[] = [
];
const LanguageSwitcher = () => {
const { locale, setLocale, t } = useI18n();
const { locale, setLocale } = useI18n();
const [open, setOpen] = useState(false);
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button variant="outline" size="sm" className="gap-2">
<button
type="button"
className="inline-flex h-8 w-8 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
>
<Globe className="h-4 w-4" />
<span className="hidden sm:inline">
{languages.find((l) => l.value === locale)?.label}
</span>
</Button>
</button>
</PopoverTrigger>
<PopoverContent className="w-32 p-1" align="end">
<div className="flex flex-col gap-1">
<div className="flex flex-col gap-0.5">
{languages.map((lang) => (
<button
key={lang.value}
className={`w-full rounded-md px-3 py-2 text-left text-sm transition-colors hover:bg-slate-100 ${
className={`w-full rounded-lg px-3 py-2 text-left text-sm transition-colors hover:bg-muted ${
locale === lang.value
? "bg-slate-100 font-medium text-slate-900"
: "text-slate-600"
? "bg-muted font-medium text-foreground"
: "text-muted-foreground"
}`}
onClick={() => {
setLocale(lang.value);

View File

@@ -138,9 +138,9 @@ const SettingsPanel = () => {
};
return (
<Card className="bg-white">
<Card>
<CardHeader>
<CardTitle>{t("settings")}</CardTitle>
<CardTitle className="text-xl">{t("settings")}</CardTitle>
<CardDescription>{t("settingsDesc")}</CardDescription>
</CardHeader>
<CardContent>
@@ -176,7 +176,7 @@ const SettingsPanel = () => {
{t("removeAvatar")}
</Button>
)}
<p className="text-xs text-slate-500">{t("avatarHint")}</p>
<p className="text-xs text-muted-foreground">{t("avatarHint")}</p>
</div>
</div>
</div>
@@ -201,11 +201,11 @@ const SettingsPanel = () => {
</div>
<div className="space-y-2">
<Label>{t("notificationChannels")}</Label>
<div className="rounded-lg bg-slate-50/80">
<div className="rounded-xl bg-muted/50 border border-border/60">
<div className="flex items-center justify-between px-4 py-3">
<div>
<div className="text-sm font-medium text-slate-800">{t("webNotifications")}</div>
<div className="text-xs text-slate-500">{t("webNotificationsDesc")}</div>
<div className="text-sm font-medium text-foreground">{t("webNotifications")}</div>
<div className="text-xs leading-relaxed text-muted-foreground">{t("webNotificationsDesc")}</div>
</div>
<Checkbox
id="inapp"
@@ -215,11 +215,11 @@ const SettingsPanel = () => {
}
/>
</div>
<div className="mx-4 h-px bg-slate-200/50" />
<div className="mx-4 h-px bg-border/60" />
<div className="flex items-center justify-between px-4 py-3">
<div>
<div className="text-sm font-medium text-slate-800">{t("barkAlerts")}</div>
<div className="text-xs text-slate-500">{t("barkAlertsDesc")}</div>
<div className="text-sm font-medium text-foreground">{t("barkAlerts")}</div>
<div className="text-xs leading-relaxed text-muted-foreground">{t("barkAlertsDesc")}</div>
</div>
<Checkbox
id="bark"
@@ -242,8 +242,8 @@ const SettingsPanel = () => {
</div>
{/* Change Password Section */}
<div className="mt-8 pt-6 border-t border-slate-200">
<h3 className="text-lg font-medium mb-4">{t("changePassword")}</h3>
<div className="mt-8 pt-6 border-t border-border">
<h3 className="text-lg font-semibold tracking-tight mb-4">{t("changePassword")}</h3>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="currentPassword">{t("currentPassword")}</Label>

View File

@@ -18,7 +18,7 @@ const AlertDialogOverlay = React.forwardRef<
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/50 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
"fixed inset-0 z-50 bg-black/40 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
@@ -36,7 +36,7 @@ const AlertDialogContent = React.forwardRef<
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 bg-white p-6 shadow-xl duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-xl",
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 bg-card p-6 shadow-2xl duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] rounded-2xl",
className
)}
{...props}
@@ -64,7 +64,7 @@ const AlertDialogTitle = React.forwardRef<
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold text-slate-900", className)}
className={cn("text-lg font-semibold text-foreground", className)}
{...props}
/>
));
@@ -76,7 +76,7 @@ const AlertDialogDescription = React.forwardRef<
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description
ref={ref}
className={cn("text-sm text-slate-500", className)}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
));

View File

@@ -5,16 +5,16 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium transition-all duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/20 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
"bg-primary text-primary-foreground shadow-sm hover:bg-primary/90 hover:shadow-md",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
"border border-border/80 bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
@@ -22,8 +22,8 @@ const buttonVariants = cva(
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
sm: "h-8 rounded-lg px-3 text-xs",
lg: "h-10 rounded-lg px-8",
icon: "h-9 w-9",
},
},

View File

@@ -6,7 +6,7 @@ const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElemen
({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("rounded-xl bg-card text-card-foreground shadow-sm", className)}
className={cn("rounded-2xl border border-border/60 bg-card text-card-foreground shadow-[0_1px_3px_0_rgb(0_0_0/0.04)]", className)}
{...props}
/>
)
@@ -15,14 +15,14 @@ Card.displayName = "Card";
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />
<div ref={ref} className={cn("flex flex-col space-y-1.5 p-6 pb-4", className)} {...props} />
)
);
CardHeader.displayName = "CardHeader";
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
<h3 ref={ref} className={cn("text-lg font-semibold leading-none tracking-tight", className)} {...props} />
<h3 ref={ref} className={cn("text-lg font-semibold leading-snug tracking-tight text-foreground", className)} {...props} />
)
);
CardTitle.displayName = "CardTitle";
@@ -31,7 +31,7 @@ const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
<p ref={ref} className={cn("mt-1 text-sm text-muted-foreground", className)} {...props} />
));
CardDescription.displayName = "CardDescription";

View File

@@ -11,13 +11,13 @@ const Checkbox = React.forwardRef<
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
"peer h-[18px] w-[18px] shrink-0 rounded-[5px] border border-border transition-colors duration-150 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/20 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:border-primary data-[state=checked]:text-primary-foreground",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator className={cn("flex items-center justify-center text-current")}>
<Check className="h-3.5 w-3.5" />
<Check className="h-3.5 w-3.5" strokeWidth={3} />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
));

View File

@@ -21,7 +21,7 @@ const DialogOverlay = React.forwardRef<
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/50 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
"fixed inset-0 z-50 bg-black/40 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
@@ -38,13 +38,13 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 bg-white p-6 shadow-xl duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-xl",
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 bg-card p-6 shadow-2xl duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] rounded-2xl",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-slate-950 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-slate-100 data-[state=open]:text-slate-500">
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-md opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-muted data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
@@ -72,7 +72,7 @@ const DialogTitle = React.forwardRef<
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold leading-none tracking-tight text-slate-900", className)}
className={cn("text-lg font-semibold leading-none tracking-tight text-foreground", className)}
{...props}
/>
));
@@ -84,7 +84,7 @@ const DialogDescription = React.forwardRef<
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-slate-500", className)}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
));

View File

@@ -8,7 +8,7 @@ const Input = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLI
<input
type={type}
className={cn(
"flex h-10 w-full rounded-lg border border-input/60 bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/30 disabled:cursor-not-allowed disabled:opacity-50",
"flex h-10 w-full rounded-xl border border-border bg-background px-3 py-2 text-sm transition-colors duration-150 ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground hover:border-primary/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/20 focus-visible:border-primary/60 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}

View File

@@ -66,7 +66,7 @@ const Sidebar = React.forwardRef<
data-state={open ? "expanded" : "collapsed"}
style={
{
"--sidebar-width": "16rem",
"--sidebar-width": "14rem",
"--sidebar-width-collapsed": "4.25rem",
...style,
} as React.CSSProperties
@@ -116,7 +116,7 @@ const SidebarGroupLabel = React.forwardRef<HTMLDivElement, React.HTMLAttributes<
({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("px-2 text-xs font-medium uppercase text-sidebar-foreground/60", className)}
className={cn("px-2 text-[11px] font-semibold uppercase tracking-wider text-sidebar-foreground/50", className)}
{...props}
/>
)
@@ -145,12 +145,12 @@ const SidebarMenuItem = React.forwardRef<HTMLLIElement, React.HTMLAttributes<HTM
SidebarMenuItem.displayName = "SidebarMenuItem";
const sidebarMenuButtonVariants = cva(
"flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium text-sidebar-foreground transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
"flex w-full items-center gap-2 rounded-lg px-3 py-2 text-[14px] font-medium text-sidebar-foreground/80 transition-all duration-150 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
{
variants: {
isActive: {
true: "bg-sidebar-accent text-sidebar-primary font-semibold",
false: "",
true: "border-l-2 border-sidebar-primary bg-sidebar-primary/[0.06] text-sidebar-primary font-semibold",
false: "border-l-2 border-transparent",
},
},
defaultVariants: {
@@ -177,7 +177,7 @@ SidebarMenuButton.displayName = "SidebarMenuButton";
const SidebarSeparator = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn("mx-3 my-2 h-px bg-sidebar-border/50", className)} {...props} />
<div ref={ref} className={cn("mx-3 my-2 h-px bg-sidebar-border/40", className)} {...props} />
)
);
SidebarSeparator.displayName = "SidebarSeparator";