mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-21 18:55:16 +02:00
refactor(header, layout): simplify header component by removing unused tabs and mobile checks; enhance RightPanel and LayoutShell with improved styling and sidebar functionality
This commit is contained in:
parent
360e21eee4
commit
65f3916fc3
6 changed files with 59 additions and 44 deletions
|
|
@ -4,12 +4,10 @@ import { useAtomValue } from "jotai";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { currentThreadAtom } from "@/atoms/chat/current-thread.atom";
|
import { currentThreadAtom } from "@/atoms/chat/current-thread.atom";
|
||||||
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
|
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
|
||||||
import { activeTabAtom, tabsAtom } from "@/atoms/tabs/tabs.atom";
|
import { activeTabAtom } from "@/atoms/tabs/tabs.atom";
|
||||||
import { ChatHeader } from "@/components/new-chat/chat-header";
|
import { ChatHeader } from "@/components/new-chat/chat-header";
|
||||||
import { ChatShareButton } from "@/components/new-chat/chat-share-button";
|
import { ChatShareButton } from "@/components/new-chat/chat-share-button";
|
||||||
import { useIsMobile } from "@/hooks/use-mobile";
|
|
||||||
import type { ChatVisibility, ThreadRecord } from "@/lib/chat/thread-persistence";
|
import type { ChatVisibility, ThreadRecord } from "@/lib/chat/thread-persistence";
|
||||||
import { RightPanelExpandButton } from "../right-panel/RightPanel";
|
|
||||||
|
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
mobileMenuTrigger?: React.ReactNode;
|
mobileMenuTrigger?: React.ReactNode;
|
||||||
|
|
@ -18,14 +16,11 @@ interface HeaderProps {
|
||||||
export function Header({ mobileMenuTrigger }: HeaderProps) {
|
export function Header({ mobileMenuTrigger }: HeaderProps) {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
|
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
|
||||||
const isMobile = useIsMobile();
|
|
||||||
const activeTab = useAtomValue(activeTabAtom);
|
const activeTab = useAtomValue(activeTabAtom);
|
||||||
const tabs = useAtomValue(tabsAtom);
|
|
||||||
|
|
||||||
const isFreePage = pathname?.startsWith("/free") ?? false;
|
const isFreePage = pathname?.startsWith("/free") ?? false;
|
||||||
const isChatPage = pathname?.includes("/new-chat") ?? false;
|
const isChatPage = pathname?.includes("/new-chat") ?? false;
|
||||||
const isDocumentTab = activeTab?.type === "document";
|
const isDocumentTab = activeTab?.type === "document";
|
||||||
const hasTabBar = tabs.length > 1;
|
|
||||||
|
|
||||||
const currentThreadState = useAtomValue(currentThreadAtom);
|
const currentThreadState = useAtomValue(currentThreadAtom);
|
||||||
|
|
||||||
|
|
@ -72,7 +67,6 @@ export function Header({ mobileMenuTrigger }: HeaderProps) {
|
||||||
{hasThread && (
|
{hasThread && (
|
||||||
<ChatShareButton thread={threadForButton} onVisibilityChange={handleVisibilityChange} />
|
<ChatShareButton thread={threadForButton} onVisibilityChange={handleVisibilityChange} />
|
||||||
)}
|
)}
|
||||||
{!isMobile && !hasTabBar && <RightPanelExpandButton />}
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,12 @@ function CollapseButton({ onClick }: { onClick: () => void }) {
|
||||||
return (
|
return (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button variant="ghost" size="icon" onClick={onClick} className="h-8 w-8 shrink-0">
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={onClick}
|
||||||
|
className="h-8 w-8 shrink-0 text-muted-foreground hover:text-foreground hover:bg-muted/40"
|
||||||
|
>
|
||||||
<PanelRight className="h-4 w-4" />
|
<PanelRight className="h-4 w-4" />
|
||||||
<span className="sr-only">Collapse panel</span>
|
<span className="sr-only">Collapse panel</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -88,7 +93,7 @@ export function RightPanelExpandButton() {
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={() => startTransition(() => setCollapsed(false))}
|
onClick={() => startTransition(() => setCollapsed(false))}
|
||||||
className="h-8 w-8 shrink-0 -m-0.5"
|
className="h-8 w-8 shrink-0 -m-0.5 text-muted-foreground hover:text-foreground hover:bg-muted/40"
|
||||||
>
|
>
|
||||||
<PanelRight className="h-4 w-4" />
|
<PanelRight className="h-4 w-4" />
|
||||||
<span className="sr-only">Expand panel</span>
|
<span className="sr-only">Expand panel</span>
|
||||||
|
|
@ -161,7 +166,7 @@ export function RightPanel({ documentsPanel }: RightPanelProps) {
|
||||||
return (
|
return (
|
||||||
<aside
|
<aside
|
||||||
style={{ width: targetWidth }}
|
style={{ width: targetWidth }}
|
||||||
className="flex h-full shrink-0 flex-col rounded-xl border bg-sidebar text-sidebar-foreground overflow-hidden transition-[width] duration-200 ease-out"
|
className="flex h-full shrink-0 flex-col border-l border-b bg-sidebar text-sidebar-foreground overflow-hidden transition-[width] duration-200 ease-out"
|
||||||
>
|
>
|
||||||
<div className="relative flex-1 min-h-0 overflow-hidden">
|
<div className="relative flex-1 min-h-0 overflow-hidden">
|
||||||
{effectiveTab === "sources" && documentsOpen && documentsPanel && (
|
{effectiveTab === "sources" && documentsOpen && documentsPanel && (
|
||||||
|
|
|
||||||
|
|
@ -120,22 +120,45 @@ interface LayoutShellProps {
|
||||||
|
|
||||||
function MainContentPanel({
|
function MainContentPanel({
|
||||||
isChatPage,
|
isChatPage,
|
||||||
|
isSidebarCollapsed,
|
||||||
onTabSwitch,
|
onTabSwitch,
|
||||||
onNewChat,
|
onNewChat,
|
||||||
leftActions,
|
leftActions,
|
||||||
|
showResizeHandle = false,
|
||||||
|
onResizeMouseDown,
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
isChatPage: boolean;
|
isChatPage: boolean;
|
||||||
|
isSidebarCollapsed: boolean;
|
||||||
onTabSwitch?: (tab: Tab) => void;
|
onTabSwitch?: (tab: Tab) => void;
|
||||||
onNewChat?: () => void;
|
onNewChat?: () => void;
|
||||||
leftActions?: React.ReactNode;
|
leftActions?: React.ReactNode;
|
||||||
|
showResizeHandle?: boolean;
|
||||||
|
onResizeMouseDown?: (e: React.MouseEvent) => void;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const activeTab = useAtomValue(activeTabAtom);
|
const activeTab = useAtomValue(activeTabAtom);
|
||||||
const isDocumentTab = activeTab?.type === "document";
|
const isDocumentTab = activeTab?.type === "document";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex flex-1 flex-col min-w-0">
|
<div
|
||||||
|
className={cn(
|
||||||
|
"relative flex flex-1 flex-col min-w-0 -ml-2",
|
||||||
|
isSidebarCollapsed ? "" : "border-l border-border/60"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{showResizeHandle && onResizeMouseDown && (
|
||||||
|
<div
|
||||||
|
role="slider"
|
||||||
|
aria-label="Resize sidebar"
|
||||||
|
aria-valuemin={0}
|
||||||
|
aria-valuemax={100}
|
||||||
|
aria-valuenow={50}
|
||||||
|
tabIndex={0}
|
||||||
|
onMouseDown={onResizeMouseDown}
|
||||||
|
className="absolute left-0 top-0 hidden md:block h-full w-2 -translate-x-1/2 cursor-col-resize z-30 focus:outline-none"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<TabBar
|
<TabBar
|
||||||
onTabSwitch={onTabSwitch}
|
onTabSwitch={onTabSwitch}
|
||||||
onNewChat={onNewChat}
|
onNewChat={onNewChat}
|
||||||
|
|
@ -143,7 +166,7 @@ function MainContentPanel({
|
||||||
rightActions={<RightPanelExpandButton />}
|
rightActions={<RightPanelExpandButton />}
|
||||||
className="min-w-0"
|
className="min-w-0"
|
||||||
/>
|
/>
|
||||||
<div className="relative flex flex-1 flex-col rounded-xl border bg-main-panel overflow-hidden min-w-0">
|
<div className="relative flex flex-1 flex-col border border-l-0 border-r-0 border-t-0 bg-main-panel overflow-hidden min-w-0">
|
||||||
<Header />
|
<Header />
|
||||||
|
|
||||||
{isDocumentTab && activeTab.documentId && activeTab.searchSpaceId ? (
|
{isDocumentTab && activeTab.documentId && activeTab.searchSpaceId ? (
|
||||||
|
|
@ -515,26 +538,14 @@ export function LayoutShell({
|
||||||
</SidebarSlideOutPanel>
|
</SidebarSlideOutPanel>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Resize handle — negative margins eat the flex gap so spacing stays unchanged */}
|
|
||||||
{!isCollapsed && (
|
|
||||||
<div
|
|
||||||
role="slider"
|
|
||||||
aria-label="Resize sidebar"
|
|
||||||
aria-valuemin={0}
|
|
||||||
aria-valuemax={100}
|
|
||||||
aria-valuenow={50}
|
|
||||||
tabIndex={0}
|
|
||||||
onMouseDown={onResizeMouseDown}
|
|
||||||
className="hidden md:block h-full cursor-col-resize z-30 focus:outline-none"
|
|
||||||
style={{ width: 8, marginLeft: -8, marginRight: -8 }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Main content panel */}
|
{/* Main content panel */}
|
||||||
<MainContentPanel
|
<MainContentPanel
|
||||||
isChatPage={isChatPage}
|
isChatPage={isChatPage}
|
||||||
|
isSidebarCollapsed={isCollapsed}
|
||||||
onTabSwitch={onTabSwitch}
|
onTabSwitch={onTabSwitch}
|
||||||
onNewChat={onNewChat}
|
onNewChat={onNewChat}
|
||||||
|
showResizeHandle={!isCollapsed}
|
||||||
|
onResizeMouseDown={onResizeMouseDown}
|
||||||
leftActions={
|
leftActions={
|
||||||
isCollapsed ? (
|
isCollapsed ? (
|
||||||
<SidebarCollapseButton isCollapsed={isCollapsed} onToggle={toggleCollapsed} />
|
<SidebarCollapseButton isCollapsed={isCollapsed} onToggle={toggleCollapsed} />
|
||||||
|
|
@ -546,12 +557,14 @@ export function LayoutShell({
|
||||||
|
|
||||||
{/* Right panel — tabbed Sources/Report (desktop only) */}
|
{/* Right panel — tabbed Sources/Report (desktop only) */}
|
||||||
{documentsPanel && (
|
{documentsPanel && (
|
||||||
<RightPanel
|
<div className="-ml-2 -mr-2">
|
||||||
documentsPanel={{
|
<RightPanel
|
||||||
open: documentsPanel.open,
|
documentsPanel={{
|
||||||
onOpenChange: documentsPanel.onOpenChange,
|
open: documentsPanel.open,
|
||||||
}}
|
onOpenChange: documentsPanel.onOpenChange,
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
|
|
||||||
|
|
@ -1207,7 +1207,7 @@ function AuthenticatedDocumentsSidebarBase({
|
||||||
|
|
||||||
const documentsContent = (
|
const documentsContent = (
|
||||||
<>
|
<>
|
||||||
<div className="shrink-0 flex h-12 items-center px-4">
|
<div className="shrink-0 flex h-12 items-center px-3 border-b">
|
||||||
<div className="flex w-full items-center justify-between">
|
<div className="flex w-full items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
|
|
@ -1692,7 +1692,7 @@ function AnonymousDocumentsSidebar({
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="shrink-0 flex h-12 items-center px-4">
|
<div className="shrink-0 flex h-12 items-center px-3 border-b">
|
||||||
<div className="flex w-full items-center justify-between">
|
<div className="flex w-full items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<h2 className="select-none text-base font-semibold">{t("title") || "Documents"}</h2>
|
<h2 className="select-none text-base font-semibold">{t("title") || "Documents"}</h2>
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,12 @@ export function SidebarCollapseButton({
|
||||||
const { shortcutKeys } = usePlatformShortcut();
|
const { shortcutKeys } = usePlatformShortcut();
|
||||||
|
|
||||||
const button = (
|
const button = (
|
||||||
<Button variant="ghost" size="icon" onClick={onToggle} className="h-8 w-8 shrink-0">
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={onToggle}
|
||||||
|
className="h-8 w-8 shrink-0 text-muted-foreground hover:text-foreground hover:bg-muted/40"
|
||||||
|
>
|
||||||
<PanelLeft className="h-4 w-4" />
|
<PanelLeft className="h-4 w-4" />
|
||||||
<span className="sr-only">{isCollapsed ? t("expand_sidebar") : t("collapse_sidebar")}</span>
|
<span className="sr-only">{isCollapsed ? t("expand_sidebar") : t("collapse_sidebar")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -77,21 +77,19 @@ export function TabBar({
|
||||||
|
|
||||||
// Keep action slots visible even with one/no tabs
|
// Keep action slots visible even with one/no tabs
|
||||||
const hasAuxActions = !!leftActions || !!rightActions || !!onNewChat;
|
const hasAuxActions = !!leftActions || !!rightActions || !!onNewChat;
|
||||||
const hasMultipleChats = tabs.length > 1;
|
|
||||||
if (tabs.length <= 1 && !hasAuxActions) return null;
|
if (tabs.length <= 1 && !hasAuxActions) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"mb-2 flex h-9 items-center shrink-0 px-1 gap-0.5 select-none",
|
"mb-0 flex h-12 items-center shrink-0 px-1 gap-0.5 select-none border-b border-border/60",
|
||||||
hasMultipleChats && "mt-1",
|
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{leftActions ? <div className="flex items-center gap-0.5 shrink-0">{leftActions}</div> : null}
|
{leftActions ? <div className="flex items-center gap-0.5 shrink-0">{leftActions}</div> : null}
|
||||||
<div
|
<div
|
||||||
ref={scrollRef}
|
ref={scrollRef}
|
||||||
className="flex h-full items-center flex-1 gap-0.5 overflow-x-auto overflow-y-hidden scrollbar-hide [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden py-1"
|
className="flex h-8 items-center flex-1 gap-3 overflow-x-auto overflow-y-hidden scrollbar-hide [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden py-0"
|
||||||
>
|
>
|
||||||
{tabs.map((tab) => {
|
{tabs.map((tab) => {
|
||||||
const isActive = tab.id === activeTabId;
|
const isActive = tab.id === activeTabId;
|
||||||
|
|
@ -103,7 +101,7 @@ export function TabBar({
|
||||||
data-tab-id={tab.id}
|
data-tab-id={tab.id}
|
||||||
onClick={() => handleTabClick(tab)}
|
onClick={() => handleTabClick(tab)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group relative flex h-full w-[150px] items-center px-3 min-h-0 overflow-hidden text-[13px] font-medium rounded-lg transition-all duration-150 shrink-0",
|
"group relative flex h-full w-[150px] items-center px-3 min-h-0 overflow-hidden text-[13px] font-medium rounded-md transition-all duration-150 shrink-0",
|
||||||
isActive
|
isActive
|
||||||
? "bg-muted/60 text-foreground"
|
? "bg-muted/60 text-foreground"
|
||||||
: "bg-transparent text-muted-foreground hover:bg-muted/30 hover:text-foreground"
|
: "bg-transparent text-muted-foreground hover:bg-muted/30 hover:text-foreground"
|
||||||
|
|
@ -136,15 +134,15 @@ export function TabBar({
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-0.5 shrink-0">
|
<div className="flex items-center gap-0.5 shrink-0 pr-2">
|
||||||
{onNewChat && (
|
{onNewChat && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onNewChat}
|
onClick={onNewChat}
|
||||||
className="flex h-6 w-6 items-center justify-center shrink-0 rounded-md text-muted-foreground transition-all duration-150 hover:text-foreground hover:bg-muted/40"
|
className="flex h-8 w-8 items-center justify-center shrink-0 rounded-md text-muted-foreground transition-all duration-150 hover:text-foreground hover:bg-muted/40"
|
||||||
title="New Chat"
|
title="New Chat"
|
||||||
>
|
>
|
||||||
<Plus className="size-3.5" />
|
<Plus className="size-4" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{rightActions}
|
{rightActions}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue