import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"; import { cva, type VariantProps } from "class-variance-authority"; import * as React from "react"; import { Counter, Icon, LinkWrapper, LinkWrapperProps, ScrollArea, ScrollBar, } from "@sparkle/components/"; import { Button } from "@sparkle/components/Button"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@sparkle/components/Collapsible"; import { MoreIcon } from "@sparkle/icons/app"; import { cn } from "@sparkle/lib/utils"; const NavigationListItemStyles = cva( cn( "s-box-border s-flex s-items-center s-w-full s-gap-1.5 s-cursor-pointer s-select-none", "s-items-center s-outline-none s-rounded-xl s-text-sm s-px-3 s-py-2 s-transition-colors s-duration-300", "data-[disabled]:s-pointer-events-none", "data-[disabled]:s-text-muted-foreground dark:data-[disabled]:s-text-muted-foreground-night", "hover:s-text-foreground dark:hover:s-text-foreground-night", "hover:s-bg-primary-100 dark:hover:s-bg-primary-200-night" ), { variants: { state: { active: "active:s-bg-primary-150 dark:active:s-bg-primary-200-night", selected: cn( "s-text-foreground dark:s-text-foreground-night", "s-bg-primary-100 dark:s-bg-primary-200-night" ), unselected: "s-text-muted-foreground dark:s-text-muted-foreground-night", }, }, defaultVariants: { state: "unselected", }, } ); interface NavigationListProps { viewportRef?: React.RefObject; } const NavigationList = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & NavigationListProps >(({ className, children, viewportRef, ...props }, ref) => { return (
{children}
); }); NavigationList.displayName = "NavigationList"; export type NavigationListItemStatus = "idle" | "unread" | "blocked" | "error"; interface NavigationListItemProps extends React.HTMLAttributes, Omit { selected?: boolean; label?: string; icon?: React.ComponentType; avatar?: React.ReactNode; moreMenu?: React.ReactNode; status?: NavigationListItemStatus; count?: number; } const NavigationListItem = React.forwardRef< HTMLDivElement, NavigationListItemProps >( ( { className, selected, label, icon, avatar, href, target, rel, replace, shallow, moreMenu, status = "idle", count, ...props }, ref ) => { const [isPressed, setIsPressed] = React.useState(false); const handleMouseDown = (event: React.MouseEvent) => { if (!(event.target as HTMLElement).closest(".button-class")) { setIsPressed(true); } }; const getStatusDotColor = () => { switch (status) { case "unread": return "s-h-2 s-w-2 s-m-1 s-bg-highlight-500 dark:s-bg-highlight-500-night"; case "blocked": return "s-h-2 s-w-2 s-m-1 s-bg-golden-400 dark:s-bg-golden-400-night"; case "error": return "s-h-2 s-w-2 s-m-1 s-bg-warning-400 dark:s-bg-warning-400-night"; default: return ""; } }; const shouldShowStatusDot = status !== "idle"; const counterValue = count && count > 0 ? count : undefined; const shouldHideStatusIndicators = Boolean(moreMenu && selected); return (
{ setIsPressed(false); }} onMouseDown={handleMouseDown} onMouseUp={() => setIsPressed(false)} > {icon && } {avatar} {label && ( {label} )} {counterValue !== undefined && !shouldHideStatusIndicators && ( )} {shouldShowStatusDot && !shouldHideStatusIndicators && (
)}
{moreMenu && <>{moreMenu}}
); } ); NavigationListItem.displayName = "NavigationListItem"; interface NavigationListItemActionProps extends React.HTMLAttributes { showOnHover?: boolean; } const NavigationListItemAction = React.forwardRef< HTMLDivElement, NavigationListItemActionProps >(({ className, ...props }, ref) => { return (
); }); NavigationListItemAction.displayName = "NavigationListItemAction"; const variantStyles = cva("", { variants: { variant: { primary: "s-text-foreground dark:s-text-foreground-night", secondary: "s-text-muted-foreground dark:s-text-muted-foreground-night", }, isSticky: { true: cn( "s-sticky s-top-0 s-z-10 s-bg-background dark:s-bg-muted-background-night", "s-border-border dark:s-border-border-night" ), }, }, defaultVariants: { variant: "primary", isSticky: false, }, }); const labelStyles = cva( "s-flex s-items-center s-justify-between s-gap-2 s-pt-4 s-pb-2 s-pr-2 s-heading-xs s-whitespace-nowrap s-overflow-hidden s-text-ellipsis" ); interface NavigationListLabelProps extends React.HTMLAttributes, VariantProps { label: string; action?: React.ReactNode; } const NavigationListLabel = React.forwardRef< HTMLDivElement, NavigationListLabelProps >(({ className, variant, label, isSticky, action, ...props }, ref) => (
{label}
{action}
)); NavigationListLabel.displayName = "NavigationListLabel"; const variantCompactStyles = cva( "s-flex s-px-2 s-py-1 s-pl-3 s-text-[10px] s-font-semibold s-text-foreground dark:s-text-foreground-night s-pt-3 s-uppercase s-whitespace-nowrap s-overflow-hidden s-text-ellipsis", { variants: { isSticky: { true: cn( "s-sticky s-top-0 s-z-10 s-bg-muted-background dark:s-bg-muted-background-night", "s-border-border dark:s-border-border-night" ), }, }, defaultVariants: { isSticky: false, }, } ); interface NavigationListCompactLabelProps extends React.HTMLAttributes, VariantProps { label: string; } const NavigationListCompactLabel = React.forwardRef< HTMLDivElement, NavigationListCompactLabelProps >(({ className, label, isSticky, ...props }, ref) => (
{label}
)); NavigationListCompactLabel.displayName = "NavigationListCompactLabel"; interface NavigationListCollapsibleSectionProps extends React.HTMLAttributes { label: string; action?: React.ReactNode; actionOnHover?: boolean; defaultOpen?: boolean; open?: boolean; onOpenChange?: (open: boolean) => void; type?: "static" | "collapse" | "collapseAndScroll"; variant?: "primary" | "secondary"; children: React.ReactNode; } const collapseableStyles = cva( cn( "s-py-2 s-px-2.5 s-w-full s-flex-1 s-text-left s-w-full", "s-heading-xs s-whitespace-nowrap s-overflow-hidden s-text-ellipsis", "s-select-none", "s-outline-none s-rounded-xl s-transition-colors s-duration-300", "data-[disabled]:s-pointer-events-none", "data-[disabled]:s-text-muted-foreground dark:data-[disabled]:s-text-muted-foreground-night" ), { variants: { variant: { primary: "s-text-foreground dark:s-text-foreground-night", secondary: "s-text-muted-foreground dark:s-text-muted-foreground-night", }, isCollapsible: { true: cn( "s-cursor-pointer s-mb-0.5", "hover:s-text-foreground dark:hover:s-text-foreground-night", "hover:s-bg-primary-100 dark:hover:s-bg-primary-200-night" ), false: "", }, }, defaultVariants: { variant: "primary", isCollapsible: false, }, } ); const NavigationListCollapsibleSection = React.forwardRef< HTMLDivElement | React.ElementRef, NavigationListCollapsibleSectionProps >( ( { label, action, actionOnHover = true, children, className, type = "static", variant = "primary", defaultOpen, open, onOpenChange, ...props }, ref ) => { const isCollapsible = type !== "static"; const labelElement = (
{label}
); const actionElement = action && (
{ e.stopPropagation(); }} > {action}
); if (type === "static") { return (
{labelElement} {actionElement}
{children}
); } const collapsibleProps = { defaultOpen, open, onOpenChange, ...props, }; if (type === "collapseAndScroll") { return (
{labelElement} {actionElement}
{children}
); } // type === "collapse" (default collapsible behavior) return (
{labelElement} {actionElement}
{children}
); } ); NavigationListCollapsibleSection.displayName = "NavigationListCollapsibleSection"; export { NavigationList, NavigationListCollapsibleSection, NavigationListCompactLabel, NavigationListItem, NavigationListItemAction, NavigationListLabel, };