Files
codeg/src/components/layout/sidebar.tsx

168 lines
5.3 KiB
TypeScript

"use client"
import { useCallback, useEffect, useRef, useState } from "react"
import {
ChevronsDownUp,
ChevronsUpDown,
Crosshair,
EllipsisVertical,
} from "lucide-react"
import { useTranslations } from "next-intl"
import { useSidebarContext } from "@/contexts/sidebar-context"
import {
SidebarConversationList,
type SidebarConversationListHandle,
} from "@/components/conversations/sidebar-conversation-list"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { useIsMobile } from "@/hooks/use-mobile"
import {
loadShowCompleted,
loadSortMode,
saveShowCompleted,
saveSortMode,
type SidebarSortMode,
} from "@/lib/sidebar-view-mode-storage"
export function Sidebar() {
const t = useTranslations("Folder.sidebar")
const { isOpen, toggle } = useSidebarContext()
const isMobile = useIsMobile()
const listRef = useRef<SidebarConversationListHandle>(null)
const [showCompleted, setShowCompleted] = useState(false)
const [sortMode, setSortMode] = useState<SidebarSortMode>("created")
const [allExpanded, setAllExpanded] = useState(true)
useEffect(() => {
// Hydrate from localStorage after mount to keep SSR/CSR markup consistent.
// eslint-disable-next-line react-hooks/set-state-in-effect
setShowCompleted(loadShowCompleted())
setSortMode(loadSortMode())
}, [])
const handleSetShowCompleted = useCallback((value: boolean) => {
setShowCompleted(value)
saveShowCompleted(value)
}, [])
const handleSetSortMode = useCallback((value: string) => {
const mode: SidebarSortMode = value === "updated" ? "updated" : "created"
setSortMode(mode)
saveSortMode(mode)
}, [])
const handleToggleExpandAll = useCallback(() => {
if (allExpanded) {
listRef.current?.collapseAll()
setAllExpanded(false)
} else {
listRef.current?.expandAll()
setAllExpanded(true)
}
}, [allExpanded])
if (!isOpen) return null
return (
<aside className="flex h-full min-h-0 flex-col overflow-hidden bg-sidebar text-sidebar-foreground select-none">
<div className="flex h-10 shrink-0 items-center justify-between gap-2 border-b border-border pl-4 pr-2">
<div className="flex min-w-0 items-baseline gap-[0.375rem]">
<h2 className="truncate text-[0.875rem] font-bold tracking-[-0.00625rem] text-sidebar-foreground">
{t("title")}
</h2>
</div>
<div className="flex items-center gap-0.5">
<Button
variant="ghost"
size="icon"
className="h-6 w-6 shrink-0 text-muted-foreground"
onClick={() => listRef.current?.scrollToActive()}
title={t("locateActiveConversation")}
>
<Crosshair className="h-3.5 w-3.5" />
</Button>
<Button
variant="ghost"
size="icon"
className="h-6 w-6 shrink-0 text-muted-foreground"
onClick={handleToggleExpandAll}
title={
allExpanded ? t("collapseAllGroups") : t("expandAllGroups")
}
>
{allExpanded ? (
<ChevronsDownUp className="h-3.5 w-3.5" />
) : (
<ChevronsUpDown className="h-3.5 w-3.5" />
)}
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-6 w-6 shrink-0 text-muted-foreground"
title={t("moreOptions")}
>
<EllipsisVertical className="h-3.5 w-3.5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuCheckboxItem
checked={showCompleted}
onCheckedChange={handleSetShowCompleted}
>
{t("showCompleted")}
</DropdownMenuCheckboxItem>
<DropdownMenuSeparator />
<DropdownMenuLabel>{t("sortBy")}</DropdownMenuLabel>
<DropdownMenuRadioGroup
value={sortMode}
onValueChange={handleSetSortMode}
>
<DropdownMenuRadioItem value="created">
{t("sortByCreatedAt")}
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="updated">
{t("sortByUpdatedAt")}
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
{/* On mobile, clicking a conversation card auto-closes the Sheet */}
<div
className="flex flex-col flex-1 min-h-0 overflow-hidden pt-1"
onClick={
isMobile
? (e) => {
const target = e.target as HTMLElement
if (target.closest("[data-conversation-id]")) {
toggle()
}
}
: undefined
}
>
<SidebarConversationList
ref={listRef}
showCompleted={showCompleted}
sortMode={sortMode}
/>
</div>
</aside>
)
}