sidebar enhancements

This commit is contained in:
miloschwartz
2025-12-18 17:54:29 -05:00
parent 1af938d7ea
commit 4f1dc19569

View File

@@ -24,7 +24,7 @@ import {
PopoverContent, PopoverContent,
PopoverTrigger PopoverTrigger
} from "@app/components/ui/popover"; } from "@app/components/ui/popover";
import { ChevronDown } from "lucide-react"; import { ChevronRight } from "lucide-react";
import { build } from "@server/build"; import { build } from "@server/build";
export type SidebarNavItem = { export type SidebarNavItem = {
@@ -51,6 +51,7 @@ export interface SidebarNavProps extends React.HTMLAttributes<HTMLElement> {
type CollapsibleNavItemProps = { type CollapsibleNavItemProps = {
item: SidebarNavItem; item: SidebarNavItem;
level: number; level: number;
isActive: boolean;
isChildActive: boolean; isChildActive: boolean;
isDisabled: boolean; isDisabled: boolean;
isCollapsed: boolean; isCollapsed: boolean;
@@ -63,6 +64,7 @@ type CollapsibleNavItemProps = {
function CollapsibleNavItem({ function CollapsibleNavItem({
item, item,
level, level,
isActive,
isChildActive, isChildActive,
isDisabled, isDisabled,
isCollapsed, isCollapsed,
@@ -112,30 +114,30 @@ function CollapsibleNavItem({
<CollapsibleTrigger asChild> <CollapsibleTrigger asChild>
<button <button
className={cn( className={cn(
"flex items-center w-full rounded transition-colors hover:bg-secondary/80 dark:hover:bg-secondary/50 rounded-md", "flex items-center w-full rounded-md transition-colors",
level === 0 ? "p-3 py-1.5" : "py-1.5", level === 0 ? "px-3 py-2" : "px-3 py-1.5",
isChildActive isActive
? "text-primary font-medium" ? "bg-secondary text-primary font-medium"
: "text-muted-foreground hover:text-foreground", : "text-muted-foreground hover:bg-secondary/80 dark:hover:bg-secondary/50 hover:text-foreground",
isDisabled && "cursor-not-allowed opacity-60" isDisabled && "cursor-not-allowed opacity-60"
)} )}
disabled={isDisabled} disabled={isDisabled}
> >
{item.icon && ( {item.icon && (
<span className="flex-shrink-0 mr-2">{item.icon}</span> <span className="flex-shrink-0 mr-3 w-5 h-5 flex items-center justify-center">{item.icon}</span>
)} )}
<div className="flex items-center gap-1.5 flex-1"> <div className="flex items-center gap-1.5 flex-1 min-w-0">
<span className="text-left">{t(item.title)}</span> <span className="text-left truncate">{t(item.title)}</span>
{item.isBeta && ( {item.isBeta && (
<Badge <Badge
variant="outline" variant="outline"
className="text-muted-foreground" className="text-muted-foreground flex-shrink-0"
> >
{t("beta")} {t("beta")}
</Badge> </Badge>
)} )}
</div> </div>
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5 flex-shrink-0 ml-2">
{build === "enterprise" && {build === "enterprise" &&
item.showEE && item.showEE &&
!isUnlocked() && ( !isUnlocked() && (
@@ -143,10 +145,10 @@ function CollapsibleNavItem({
{t("licenseBadge")} {t("licenseBadge")}
</Badge> </Badge>
)} )}
<ChevronDown <ChevronRight
className={cn( className={cn(
"h-4 w-4 transition-transform duration-300 ease-in-out", "h-4 w-4 transition-transform duration-300 ease-in-out text-muted-foreground",
"group-data-[state=open]/collapsible:rotate-180" "group-data-[state=open]/collapsible:rotate-90"
)} )}
/> />
</div> </div>
@@ -155,7 +157,7 @@ function CollapsibleNavItem({
<CollapsibleContent> <CollapsibleContent>
<div <div
className={cn( className={cn(
"border-l ml-3 pl-2 mt-1 space-y-1", "border-l ml-3 pl-3 mt-0 space-y-0",
"border-border" "border-border"
)} )}
> >
@@ -236,6 +238,7 @@ export function SidebarNav({
key={item.title} key={item.title}
item={item} item={item}
level={level} level={level}
isActive={isActive}
isChildActive={isChildActive} isChildActive={isChildActive}
isDisabled={isDisabled || false} isDisabled={isDisabled || false}
isCollapsed={isCollapsed} isCollapsed={isCollapsed}
@@ -252,11 +255,11 @@ export function SidebarNav({
<Link <Link
href={isDisabled ? "#" : hydratedHref} href={isDisabled ? "#" : hydratedHref}
className={cn( className={cn(
"flex items-center rounded transition-colors hover:bg-secondary/80 dark:hover:bg-secondary/50 rounded-md", "flex items-center rounded-md transition-colors",
isCollapsed ? "px-2 py-2 justify-center" : "px-3 py-1.5", isCollapsed ? "px-2 py-2 justify-center" : level === 0 ? "px-3 py-2" : "px-3 py-1.5",
isActive isActive
? "text-primary font-medium" ? "bg-secondary text-primary font-medium"
: "text-muted-foreground hover:text-foreground", : "text-muted-foreground hover:bg-secondary/80 dark:hover:bg-secondary/50 hover:text-foreground",
isDisabled && "cursor-not-allowed opacity-60" isDisabled && "cursor-not-allowed opacity-60"
)} )}
onClick={(e) => { onClick={(e) => {
@@ -271,19 +274,22 @@ export function SidebarNav({
> >
{item.icon && ( {item.icon && (
<span <span
className={cn("flex-shrink-0", !isCollapsed && "mr-2")} className={cn(
"flex-shrink-0 w-5 h-5 flex items-center justify-center",
!isCollapsed && "mr-3"
)}
> >
{item.icon} {item.icon}
</span> </span>
)} )}
{!isCollapsed && ( {!isCollapsed && (
<> <>
<div className="flex items-center gap-1.5 flex-1"> <div className="flex items-center gap-1.5 flex-1 min-w-0">
<span>{t(item.title)}</span> <span className="truncate">{t(item.title)}</span>
{item.isBeta && ( {item.isBeta && (
<Badge <Badge
variant="outline" variant="outline"
className="text-muted-foreground" className="text-muted-foreground flex-shrink-0"
> >
{t("beta")} {t("beta")}
</Badge> </Badge>
@@ -292,7 +298,7 @@ export function SidebarNav({
{build === "enterprise" && {build === "enterprise" &&
item.showEE && item.showEE &&
!isUnlocked() && ( !isUnlocked() && (
<Badge variant="outlinePrimary"> <Badge variant="outlinePrimary" className="flex-shrink-0">
{t("licenseBadge")} {t("licenseBadge")}
</Badge> </Badge>
)} )}
@@ -302,27 +308,28 @@ export function SidebarNav({
) : ( ) : (
<div <div
className={cn( className={cn(
"flex items-center rounded transition-colors px-3 py-1.5", "flex items-center rounded-md transition-colors",
level === 0 ? "px-3 py-2" : "px-3 py-1.5",
"text-muted-foreground", "text-muted-foreground",
isDisabled && "cursor-not-allowed opacity-60" isDisabled && "cursor-not-allowed opacity-60"
)} )}
> >
{item.icon && ( {item.icon && (
<span className="flex-shrink-0 mr-2">{item.icon}</span> <span className="flex-shrink-0 mr-3 w-5 h-5 flex items-center justify-center">{item.icon}</span>
)} )}
<div className="flex items-center gap-1.5 flex-1"> <div className="flex items-center gap-1.5 flex-1 min-w-0">
<span>{t(item.title)}</span> <span className="truncate">{t(item.title)}</span>
{item.isBeta && ( {item.isBeta && (
<Badge <Badge
variant="outline" variant="outline"
className="text-muted-foreground" className="text-muted-foreground flex-shrink-0"
> >
{t("beta")} {t("beta")}
</Badge> </Badge>
)} )}
</div> </div>
{build === "enterprise" && item.showEE && !isUnlocked() && ( {build === "enterprise" && item.showEE && !isUnlocked() && (
<Badge variant="outlinePrimary">{t("licenseBadge")}</Badge> <Badge variant="outlinePrimary" className="flex-shrink-0 ml-2">{t("licenseBadge")}</Badge>
)} )}
</div> </div>
); );
@@ -338,17 +345,17 @@ export function SidebarNav({
<TooltipTrigger asChild> <TooltipTrigger asChild>
<button <button
className={cn( className={cn(
"flex items-center rounded transition-colors hover:bg-secondary/80 dark:hover:bg-secondary/50 rounded-md px-2 py-2 justify-center w-full", "flex items-center rounded-md transition-colors px-2 py-2 justify-center w-full",
isChildActive isActive
? "text-primary font-medium" ? "bg-secondary text-primary font-medium"
: "text-muted-foreground hover:text-foreground", : "text-muted-foreground hover:bg-secondary/80 dark:hover:bg-secondary/50 hover:text-foreground",
isDisabled && isDisabled &&
"cursor-not-allowed opacity-60" "cursor-not-allowed opacity-60"
)} )}
disabled={isDisabled} disabled={isDisabled}
> >
{item.icon && ( {item.icon && (
<span className="flex-shrink-0"> <span className="flex-shrink-0 w-5 h-5 flex items-center justify-center">
{item.icon} {item.icon}
</span> </span>
)} )}
@@ -393,7 +400,7 @@ export function SidebarNav({
: childHydratedHref : childHydratedHref
} }
className={cn( className={cn(
"flex items-center rounded transition-colors px-3 py-1.5 text-sm", "flex items-center rounded-md transition-colors px-3 py-1.5 text-sm",
childIsActive childIsActive
? "bg-secondary text-primary font-medium" ? "bg-secondary text-primary font-medium"
: "text-muted-foreground hover:bg-secondary/50 hover:text-foreground", : "text-muted-foreground hover:bg-secondary/50 hover:text-foreground",
@@ -411,18 +418,18 @@ export function SidebarNav({
}} }}
> >
{childItem.icon && ( {childItem.icon && (
<span className="flex-shrink-0 mr-2"> <span className="flex-shrink-0 mr-3 w-5 h-5 flex items-center justify-center">
{childItem.icon} {childItem.icon}
</span> </span>
)} )}
<div className="flex items-center gap-1.5 flex-1"> <div className="flex items-center gap-1.5 flex-1 min-w-0">
<span> <span className="truncate">
{t(childItem.title)} {t(childItem.title)}
</span> </span>
{childItem.isBeta && ( {childItem.isBeta && (
<Badge <Badge
variant="outline" variant="outline"
className="text-muted-foreground" className="text-muted-foreground flex-shrink-0"
> >
{t("beta")} {t("beta")}
</Badge> </Badge>
@@ -431,7 +438,7 @@ export function SidebarNav({
{build === "enterprise" && {build === "enterprise" &&
childItem.showEE && childItem.showEE &&
!isUnlocked() && ( !isUnlocked() && (
<Badge variant="outlinePrimary"> <Badge variant="outlinePrimary" className="flex-shrink-0 ml-2">
{t( {t(
"licenseBadge" "licenseBadge"
)} )}
@@ -467,20 +474,20 @@ export function SidebarNav({
return ( return (
<nav <nav
className={cn( className={cn(
"flex flex-col gap-2 text-sm", "flex flex-col text-sm",
disabled && "pointer-events-none opacity-60", disabled && "pointer-events-none opacity-60",
className className
)} )}
{...props} {...props}
> >
{sections.map((section) => ( {sections.map((section, sectionIndex) => (
<div key={section.heading} className="mb-2"> <div key={section.heading} className={cn(sectionIndex > 0 && "mt-4")}>
{!isCollapsed && ( {!isCollapsed && (
<div className="px-3 py-1 text-xs font-semibold text-muted-foreground/70 uppercase tracking-wide"> <div className="px-3 py-2 text-xs font-medium text-muted-foreground/80 uppercase tracking-wider">
{t(`${section.heading}`)} {t(`${section.heading}`)}
</div> </div>
)} )}
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-0">
{section.items.map((item) => renderNavItem(item, 0))} {section.items.map((item) => renderNavItem(item, 0))}
</div> </div>
</div> </div>