refactor: unify sidebar state management in LayoutDataProvider to allow only one slide-out panel open at a time

This commit is contained in:
Anish Sarkar 2026-03-22 00:01:31 +05:30
parent 7d33a69e20
commit 020d806b1f
7 changed files with 333 additions and 258 deletions

View file

@ -140,8 +140,7 @@ interface TabDataSource {
markAllAsRead: () => Promise<boolean>;
}
interface InboxSidebarProps {
open: boolean;
export interface InboxSidebarContentProps {
onOpenChange: (open: boolean) => void;
comments: TabDataSource;
status: TabDataSource;
@ -149,14 +148,17 @@ interface InboxSidebarProps {
onCloseMobileSidebar?: () => void;
}
export function InboxSidebar({
open,
interface InboxSidebarProps extends InboxSidebarContentProps {
open: boolean;
}
export function InboxSidebarContent({
onOpenChange,
comments,
status,
totalUnreadCount,
onCloseMobileSidebar,
}: InboxSidebarProps) {
}: InboxSidebarContentProps) {
const t = useTranslations("sidebar");
const router = useRouter();
const params = useParams();
@ -199,7 +201,7 @@ export function InboxSidebar({
},
}),
staleTime: 30 * 1000,
enabled: isSearchMode && open,
enabled: isSearchMode,
});
useEffect(() => {
@ -207,23 +209,13 @@ export function InboxSidebar({
}, []);
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === "Escape" && open) {
onOpenChange(false);
}
};
document.addEventListener("keydown", handleEscape);
return () => document.removeEventListener("keydown", handleEscape);
}, [open, onOpenChange]);
useEffect(() => {
if (!open || !isMobile) return;
if (!isMobile) return;
const originalOverflow = document.body.style.overflow;
document.body.style.overflow = "hidden";
return () => {
document.body.style.overflow = originalOverflow;
};
}, [open, isMobile]);
}, [isMobile]);
useEffect(() => {
if (activeTab !== "status") {
@ -239,7 +231,7 @@ export function InboxSidebar({
queryKey: cacheKeys.notifications.sourceTypes(searchSpaceId),
queryFn: () => notificationsApiService.getSourceTypes(searchSpaceId ?? undefined),
staleTime: 60 * 1000,
enabled: open && activeTab === "status",
enabled: activeTab === "status",
});
const statusSourceOptions = useMemo(() => {
@ -327,7 +319,7 @@ export function InboxSidebar({
// Infinite scroll — uses active tab's pagination
useEffect(() => {
if (!activeSource.hasMore || activeSource.loadingMore || !open || isSearchMode) return;
if (!activeSource.hasMore || activeSource.loadingMore || isSearchMode) return;
const observer = new IntersectionObserver(
(entries) => {
@ -347,7 +339,7 @@ export function InboxSidebar({
}
return () => observer.disconnect();
}, [activeSource.hasMore, activeSource.loadingMore, activeSource.loadMore, open, isSearchMode]);
}, [activeSource.hasMore, activeSource.loadingMore, activeSource.loadMore, isSearchMode]);
const handleItemClick = useCallback(
async (item: InboxItem) => {
@ -522,7 +514,7 @@ export function InboxSidebar({
const isLoading = isSearchMode ? isSearchLoading : activeSource.loading;
const inboxContent = (
return (
<>
<div className="shrink-0 p-4 pb-2 space-y-3">
<div className="flex items-center justify-between">
@ -546,7 +538,7 @@ export function InboxSidebar({
<Button
variant="ghost"
size="icon"
className="h-8 w-8 rounded-full"
className="h-7 w-7 rounded-full"
onClick={() => setFilterDrawerOpen(true)}
>
<ListFilter className="h-4 w-4 text-muted-foreground" />
@ -694,7 +686,7 @@ export function InboxSidebar({
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8 rounded-full">
<Button variant="ghost" size="icon" className="h-7 w-7 rounded-full">
<ListFilter className="h-4 w-4 text-muted-foreground" />
<span className="sr-only">{t("filter") || "Filter"}</span>
</Button>
@ -790,7 +782,7 @@ export function InboxSidebar({
<Button
variant="ghost"
size="icon"
className="h-8 w-8 rounded-full"
className="h-7 w-7 rounded-full"
onClick={handleMarkAllAsRead}
disabled={totalUnreadCount === 0}
>
@ -803,7 +795,7 @@ export function InboxSidebar({
<Button
variant="ghost"
size="icon"
className="h-8 w-8 rounded-full"
className="h-7 w-7 rounded-full"
onClick={handleMarkAllAsRead}
disabled={totalUnreadCount === 0}
>
@ -1051,10 +1043,27 @@ export function InboxSidebar({
</div>
</>
);
}
export function InboxSidebar({
open,
onOpenChange,
comments,
status,
totalUnreadCount,
onCloseMobileSidebar,
}: InboxSidebarProps) {
const t = useTranslations("sidebar");
return (
<SidebarSlideOutPanel open={open} onOpenChange={onOpenChange} ariaLabel={t("inbox") || "Inbox"}>
{inboxContent}
<InboxSidebarContent
onOpenChange={onOpenChange}
comments={comments}
status={status}
totalUnreadCount={totalUnreadCount}
onCloseMobileSidebar={onCloseMobileSidebar}
/>
</SidebarSlideOutPanel>
);
}