Skip to content

Commit f73c1d8

Browse files
committed
feat: Enhance report panel state management and dropdown functionality in sidebar
1 parent 4a576f7 commit f73c1d8

File tree

4 files changed

+62
-37
lines changed

4 files changed

+62
-37
lines changed

surfsense_web/atoms/chat/report-panel.atom.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { atom } from "jotai";
2-
import { documentsSidebarOpenAtom } from "@/atoms/documents/ui.atoms";
32
import { rightPanelCollapsedAtom, rightPanelTabAtom } from "@/atoms/layout/right-panel.atom";
43

54
interface ReportPanelState {
@@ -25,11 +24,14 @@ export const reportPanelAtom = atom<ReportPanelState>(initialState);
2524
/** Derived read-only atom for checking if panel is open */
2625
export const reportPanelOpenAtom = atom((get) => get(reportPanelAtom).isOpen);
2726

27+
/** Snapshot of `rightPanelCollapsedAtom` taken before the report opens */
28+
const preReportCollapsedAtom = atom<boolean | null>(null);
29+
2830
/** Action atom to open the report panel with a specific report */
2931
export const openReportPanelAtom = atom(
3032
null,
3133
(
32-
_get,
34+
get,
3335
set,
3436
{
3537
reportId,
@@ -38,6 +40,9 @@ export const openReportPanelAtom = atom(
3840
shareToken,
3941
}: { reportId: number; title: string; wordCount?: number; shareToken?: string | null }
4042
) => {
43+
if (!get(reportPanelAtom).isOpen) {
44+
set(preReportCollapsedAtom, get(rightPanelCollapsedAtom));
45+
}
4146
set(reportPanelAtom, {
4247
isOpen: true,
4348
reportId,
@@ -47,12 +52,16 @@ export const openReportPanelAtom = atom(
4752
});
4853
set(rightPanelTabAtom, "report");
4954
set(rightPanelCollapsedAtom, false);
50-
set(documentsSidebarOpenAtom, true);
5155
}
5256
);
5357

5458
/** Action atom to close the report panel */
55-
export const closeReportPanelAtom = atom(null, (_get, set) => {
59+
export const closeReportPanelAtom = atom(null, (get, set) => {
5660
set(reportPanelAtom, initialState);
5761
set(rightPanelTabAtom, "sources");
62+
const prev = get(preReportCollapsedAtom);
63+
if (prev !== null) {
64+
set(rightPanelCollapsedAtom, prev);
65+
set(preReportCollapsedAtom, null);
66+
}
5867
});

surfsense_web/components/assistant-ui/thread.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,7 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
627627
side="bottom"
628628
align="start"
629629
sideOffset={12}
630-
className="w-[calc(100vw-2rem)] max-w-56 sm:max-w-72 sm:w-72 p-0"
630+
className="w-[calc(100vw-2rem)] max-w-56 sm:max-w-72 sm:w-72 p-0 select-none"
631631
onOpenAutoFocus={(e) => e.preventDefault()}
632632
>
633633
<div className="flex items-center justify-between px-2.5 py-2 sm:px-3 sm:py-2.5 border-b">

surfsense_web/components/layout/ui/sidebar/ChatListItem.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
DropdownMenu,
99
DropdownMenuContent,
1010
DropdownMenuItem,
11-
DropdownMenuSeparator,
1211
DropdownMenuTrigger,
1312
} from "@/components/ui/dropdown-menu";
1413
import { useLongPress } from "@/hooks/use-long-press";
@@ -20,6 +19,8 @@ interface ChatListItemProps {
2019
name: string;
2120
isActive?: boolean;
2221
archived?: boolean;
22+
dropdownOpen?: boolean;
23+
onDropdownOpenChange?: (open: boolean) => void;
2324
onClick?: () => void;
2425
onRename?: () => void;
2526
onArchive?: () => void;
@@ -30,18 +31,22 @@ export function ChatListItem({
3031
name,
3132
isActive,
3233
archived,
34+
dropdownOpen: controlledOpen,
35+
onDropdownOpenChange,
3336
onClick,
3437
onRename,
3538
onArchive,
3639
onDelete,
3740
}: ChatListItemProps) {
3841
const t = useTranslations("sidebar");
3942
const isMobile = useIsMobile();
40-
const [dropdownOpen, setDropdownOpen] = useState(false);
43+
const [internalOpen, setInternalOpen] = useState(false);
44+
const dropdownOpen = controlledOpen ?? internalOpen;
45+
const setDropdownOpen = onDropdownOpenChange ?? setInternalOpen;
4146
const animatedName = useTypewriter(name);
4247

4348
const { handlers: longPressHandlers, wasLongPress } = useLongPress(
44-
useCallback(() => setDropdownOpen(true), [])
49+
useCallback(() => setDropdownOpen(true), [setDropdownOpen])
4550
);
4651

4752
const handleClick = useCallback(() => {
@@ -68,20 +73,20 @@ export function ChatListItem({
6873
{/* Actions dropdown - trigger hidden on mobile, long-press opens it instead */}
6974
<div
7075
className={cn(
71-
"absolute right-0 top-0 bottom-0 flex items-center pr-1 pl-6 rounded-r-md",
76+
"pointer-events-none absolute right-0 top-0 bottom-0 flex items-center pr-1 pl-6 rounded-r-md",
7277
isActive
7378
? "bg-gradient-to-l from-accent from-60% to-transparent"
7479
: "bg-gradient-to-l from-sidebar from-60% to-transparent group-hover/item:from-accent",
7580
isMobile
76-
? "opacity-0 pointer-events-none"
81+
? "opacity-0"
7782
: isActive
7883
? "opacity-100"
7984
: "opacity-0 group-hover/item:opacity-100"
8085
)}
8186
>
8287
<DropdownMenu open={dropdownOpen} onOpenChange={setDropdownOpen}>
8388
<DropdownMenuTrigger asChild>
84-
<Button variant="ghost" size="icon" className="h-6 w-6">
89+
<Button variant="ghost" size="icon" className="pointer-events-auto h-6 w-6">
8590
<MoreHorizontal className="h-3.5 w-3.5 text-muted-foreground" />
8691
<span className="sr-only">{t("more_options")}</span>
8792
</Button>
@@ -118,8 +123,7 @@ export function ChatListItem({
118123
)}
119124
</DropdownMenuItem>
120125
)}
121-
{onArchive && onDelete && <DropdownMenuSeparator />}
122-
{onDelete && (
126+
{onDelete && (
123127
<DropdownMenuItem
124128
onClick={(e) => {
125129
e.stopPropagation();

surfsense_web/components/layout/ui/sidebar/Sidebar.tsx

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { FolderOpen, PenSquare } from "lucide-react";
44
import { useTranslations } from "next-intl";
5+
import { useState } from "react";
56
import { Button } from "@/components/ui/button";
67
import { Skeleton } from "@/components/ui/skeleton";
78
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
@@ -89,6 +90,7 @@ export function Sidebar({
8990
isResizing = false,
9091
}: SidebarProps) {
9192
const t = useTranslations("sidebar");
93+
const [openDropdownChatId, setOpenDropdownChatId] = useState<number | null>(null);
9294

9395
return (
9496
<div
@@ -103,6 +105,12 @@ export function Sidebar({
103105
{/* Resize handle on right border */}
104106
{!isCollapsed && onResizeMouseDown && (
105107
<div
108+
role="slider"
109+
aria-label="Resize sidebar"
110+
aria-valuemin={0}
111+
aria-valuemax={100}
112+
aria-valuenow={50}
113+
tabIndex={0}
106114
onMouseDown={onResizeMouseDown}
107115
className="absolute right-0 top-0 h-full w-1 cursor-col-resize hover:bg-border active:bg-border z-10"
108116
/>
@@ -209,18 +217,20 @@ export function Sidebar({
209217
<div
210218
className={`flex flex-col gap-0.5 max-h-full overflow-y-auto scrollbar-thin scrollbar-thumb-muted-foreground/20 scrollbar-track-transparent ${sharedChats.length > 4 ? "pb-8" : ""}`}
211219
>
212-
{sharedChats.slice(0, 20).map((chat) => (
213-
<ChatListItem
214-
key={chat.id}
215-
name={chat.name}
216-
isActive={chat.id === activeChatId}
217-
archived={chat.archived}
218-
onClick={() => onChatSelect(chat)}
219-
onRename={() => onChatRename?.(chat)}
220-
onArchive={() => onChatArchive?.(chat)}
221-
onDelete={() => onChatDelete?.(chat)}
222-
/>
223-
))}
220+
{sharedChats.slice(0, 20).map((chat) => (
221+
<ChatListItem
222+
key={chat.id}
223+
name={chat.name}
224+
isActive={chat.id === activeChatId}
225+
archived={chat.archived}
226+
dropdownOpen={openDropdownChatId === chat.id}
227+
onDropdownOpenChange={(open) => setOpenDropdownChatId(open ? chat.id : null)}
228+
onClick={() => onChatSelect(chat)}
229+
onRename={() => onChatRename?.(chat)}
230+
onArchive={() => onChatArchive?.(chat)}
231+
onDelete={() => onChatDelete?.(chat)}
232+
/>
233+
))}
224234
</div>
225235
{/* Gradient fade indicator when more than 4 items */}
226236
{sharedChats.length > 4 && (
@@ -281,18 +291,20 @@ export function Sidebar({
281291
<div
282292
className={`flex flex-col gap-0.5 h-full overflow-y-auto scrollbar-thin scrollbar-thumb-muted-foreground/20 scrollbar-track-transparent ${chats.length > 4 ? "pb-8" : ""}`}
283293
>
284-
{chats.slice(0, 20).map((chat) => (
285-
<ChatListItem
286-
key={chat.id}
287-
name={chat.name}
288-
isActive={chat.id === activeChatId}
289-
archived={chat.archived}
290-
onClick={() => onChatSelect(chat)}
291-
onRename={() => onChatRename?.(chat)}
292-
onArchive={() => onChatArchive?.(chat)}
293-
onDelete={() => onChatDelete?.(chat)}
294-
/>
295-
))}
294+
{chats.slice(0, 20).map((chat) => (
295+
<ChatListItem
296+
key={chat.id}
297+
name={chat.name}
298+
isActive={chat.id === activeChatId}
299+
archived={chat.archived}
300+
dropdownOpen={openDropdownChatId === chat.id}
301+
onDropdownOpenChange={(open) => setOpenDropdownChatId(open ? chat.id : null)}
302+
onClick={() => onChatSelect(chat)}
303+
onRename={() => onChatRename?.(chat)}
304+
onArchive={() => onChatArchive?.(chat)}
305+
onDelete={() => onChatDelete?.(chat)}
306+
/>
307+
))}
296308
</div>
297309
{/* Gradient fade indicator when more than 4 items */}
298310
{chats.length > 4 && (

0 commit comments

Comments
 (0)