mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-06-30 20:39:46 +02:00
added search bar to top bar
This commit is contained in:
parent
ac30af4d10
commit
5f56a842e2
3 changed files with 84 additions and 154 deletions
|
|
@ -6,7 +6,7 @@ import type { LanguageModelUsage, ToolUIPart } from 'ai';
|
||||||
import './App.css'
|
import './App.css'
|
||||||
import z from 'zod';
|
import z from 'zod';
|
||||||
import { Button } from './components/ui/button';
|
import { Button } from './components/ui/button';
|
||||||
import { CheckIcon, LoaderIcon, ArrowUp, PanelLeftIcon, PanelRightIcon, Square, X, ChevronLeftIcon, ChevronRightIcon, SquarePen } from 'lucide-react';
|
import { CheckIcon, LoaderIcon, ArrowUp, PanelLeftIcon, PanelRightIcon, Search, Square, X, ChevronLeftIcon, ChevronRightIcon, SquarePen } from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { MarkdownEditor } from './components/markdown-editor';
|
import { MarkdownEditor } from './components/markdown-editor';
|
||||||
import { ChatInputBar } from './components/chat-button';
|
import { ChatInputBar } from './components/chat-button';
|
||||||
|
|
@ -14,7 +14,7 @@ import { ChatSidebar } from './components/chat-sidebar';
|
||||||
import { GraphView, type GraphEdge, type GraphNode } from '@/components/graph-view';
|
import { GraphView, type GraphEdge, type GraphNode } from '@/components/graph-view';
|
||||||
import { useDebounce } from './hooks/use-debounce';
|
import { useDebounce } from './hooks/use-debounce';
|
||||||
import { SidebarContentPanel } from '@/components/sidebar-content';
|
import { SidebarContentPanel } from '@/components/sidebar-content';
|
||||||
import { SidebarSectionProvider, type ActiveSection } from '@/contexts/sidebar-context';
|
import { SidebarSectionProvider, useSidebarSection, type ActiveSection } from '@/contexts/sidebar-context';
|
||||||
import {
|
import {
|
||||||
Conversation,
|
Conversation,
|
||||||
ConversationContent,
|
ConversationContent,
|
||||||
|
|
@ -131,8 +131,8 @@ const TITLEBAR_BUTTON_PX = 32
|
||||||
const TITLEBAR_BUTTON_GAP_PX = 4
|
const TITLEBAR_BUTTON_GAP_PX = 4
|
||||||
const TITLEBAR_HEADER_GAP_PX = 8
|
const TITLEBAR_HEADER_GAP_PX = 8
|
||||||
const TITLEBAR_TOGGLE_MARGIN_LEFT_PX = 12
|
const TITLEBAR_TOGGLE_MARGIN_LEFT_PX = 12
|
||||||
const TITLEBAR_BUTTONS_COLLAPSED = 4
|
const TITLEBAR_BUTTONS_COLLAPSED = 5
|
||||||
const TITLEBAR_BUTTON_GAPS_COLLAPSED = 3
|
const TITLEBAR_BUTTON_GAPS_COLLAPSED = 4
|
||||||
|
|
||||||
const clampNumber = (value: number, min: number, max: number) =>
|
const clampNumber = (value: number, min: number, max: number) =>
|
||||||
Math.min(max, Math.max(min, value))
|
Math.min(max, Math.max(min, value))
|
||||||
|
|
@ -487,6 +487,7 @@ function FixedSidebarToggle({
|
||||||
leftInsetPx: number
|
leftInsetPx: number
|
||||||
}) {
|
}) {
|
||||||
const { toggleSidebar, state } = useSidebar()
|
const { toggleSidebar, state } = useSidebar()
|
||||||
|
const { searchOpen, setSearchOpen } = useSidebarSection()
|
||||||
const isCollapsed = state === "collapsed"
|
const isCollapsed = state === "collapsed"
|
||||||
return (
|
return (
|
||||||
<div className="fixed left-0 top-0 z-50 flex h-10 items-center" style={{ WebkitAppRegion: 'no-drag' } as React.CSSProperties}>
|
<div className="fixed left-0 top-0 z-50 flex h-10 items-center" style={{ WebkitAppRegion: 'no-drag' } as React.CSSProperties}>
|
||||||
|
|
@ -510,6 +511,20 @@ function FixedSidebarToggle({
|
||||||
>
|
>
|
||||||
<SquarePen className="size-5" />
|
<SquarePen className="size-5" />
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setSearchOpen(!searchOpen)}
|
||||||
|
className={cn(
|
||||||
|
"flex h-8 w-8 items-center justify-center rounded-md transition-colors",
|
||||||
|
searchOpen
|
||||||
|
? "text-foreground bg-accent"
|
||||||
|
: "text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
|
)}
|
||||||
|
style={{ marginLeft: TITLEBAR_BUTTON_GAP_PX }}
|
||||||
|
aria-label="Search"
|
||||||
|
>
|
||||||
|
<Search className="size-5" />
|
||||||
|
</button>
|
||||||
{/* Back / Forward navigation */}
|
{/* Back / Forward navigation */}
|
||||||
{isCollapsed && (
|
{isCollapsed && (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -401,8 +401,7 @@ export function SidebarContentPanel({
|
||||||
selectedBackgroundTask,
|
selectedBackgroundTask,
|
||||||
...props
|
...props
|
||||||
}: SidebarContentPanelProps) {
|
}: SidebarContentPanelProps) {
|
||||||
const { activeSection, setActiveSection } = useSidebarSection()
|
const { activeSection, setActiveSection, searchOpen, setSearchOpen } = useSidebarSection()
|
||||||
const [searchOpen, setSearchOpen] = useState(false)
|
|
||||||
const [searchQuery, setSearchQuery] = useState("")
|
const [searchQuery, setSearchQuery] = useState("")
|
||||||
const searchInputRef = useRef<HTMLInputElement>(null)
|
const searchInputRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
|
|
@ -437,7 +436,7 @@ export function SidebarContentPanel({
|
||||||
<SidebarHeader className="titlebar-drag-region">
|
<SidebarHeader className="titlebar-drag-region">
|
||||||
{/* Top spacer to clear the traffic lights + fixed toggle row */}
|
{/* Top spacer to clear the traffic lights + fixed toggle row */}
|
||||||
<div className="h-8" />
|
<div className="h-8" />
|
||||||
{/* Tab switcher - centered below the traffic lights row */}
|
{/* Tab switcher */}
|
||||||
<div className="flex items-center px-2 py-1.5">
|
<div className="flex items-center px-2 py-1.5">
|
||||||
<div className="titlebar-no-drag flex w-full rounded-lg bg-sidebar-accent/50 p-0.5">
|
<div className="titlebar-no-drag flex w-full rounded-lg bg-sidebar-accent/50 p-0.5">
|
||||||
{sectionTabs.map((tab) => (
|
{sectionTabs.map((tab) => (
|
||||||
|
|
@ -456,6 +455,29 @@ export function SidebarContentPanel({
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{searchOpen && (
|
||||||
|
<div className="titlebar-no-drag flex items-center gap-1 rounded-md border border-sidebar-border bg-sidebar-accent/30 mx-2 mb-1.5 px-2 py-1">
|
||||||
|
<Search className="size-3.5 shrink-0 text-muted-foreground" />
|
||||||
|
<input
|
||||||
|
ref={searchInputRef}
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Escape") setSearchOpen(false)
|
||||||
|
}}
|
||||||
|
placeholder={activeSection === "tasks" ? "Search chats..." : "Search files..."}
|
||||||
|
className="flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground/60"
|
||||||
|
/>
|
||||||
|
{searchQuery && (
|
||||||
|
<button
|
||||||
|
onClick={() => setSearchQuery("")}
|
||||||
|
className="rounded p-0.5 text-muted-foreground hover:text-sidebar-foreground transition-colors"
|
||||||
|
>
|
||||||
|
<X className="size-3.5" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
<SidebarContent>
|
<SidebarContent>
|
||||||
{activeSection === "knowledge" && (
|
{activeSection === "knowledge" && (
|
||||||
|
|
@ -466,11 +488,6 @@ export function SidebarContentPanel({
|
||||||
onSelectFile={onSelectFile}
|
onSelectFile={onSelectFile}
|
||||||
actions={knowledgeActions}
|
actions={knowledgeActions}
|
||||||
onVoiceNoteCreated={onVoiceNoteCreated}
|
onVoiceNoteCreated={onVoiceNoteCreated}
|
||||||
searchOpen={searchOpen}
|
|
||||||
setSearchOpen={setSearchOpen}
|
|
||||||
searchQuery={searchQuery}
|
|
||||||
setSearchQuery={setSearchQuery}
|
|
||||||
searchInputRef={searchInputRef}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activeSection === "tasks" && (
|
{activeSection === "tasks" && (
|
||||||
|
|
@ -481,11 +498,6 @@ export function SidebarContentPanel({
|
||||||
actions={tasksActions}
|
actions={tasksActions}
|
||||||
backgroundTasks={filteredBackgroundTasks}
|
backgroundTasks={filteredBackgroundTasks}
|
||||||
selectedBackgroundTask={selectedBackgroundTask}
|
selectedBackgroundTask={selectedBackgroundTask}
|
||||||
searchOpen={searchOpen}
|
|
||||||
setSearchOpen={setSearchOpen}
|
|
||||||
searchQuery={searchQuery}
|
|
||||||
setSearchQuery={setSearchQuery}
|
|
||||||
searchInputRef={searchInputRef}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
|
|
@ -762,11 +774,6 @@ function KnowledgeSection({
|
||||||
onSelectFile,
|
onSelectFile,
|
||||||
actions,
|
actions,
|
||||||
onVoiceNoteCreated,
|
onVoiceNoteCreated,
|
||||||
searchOpen,
|
|
||||||
setSearchOpen,
|
|
||||||
searchQuery,
|
|
||||||
setSearchQuery,
|
|
||||||
searchInputRef,
|
|
||||||
}: {
|
}: {
|
||||||
tree: TreeNode[]
|
tree: TreeNode[]
|
||||||
selectedPath: string | null
|
selectedPath: string | null
|
||||||
|
|
@ -774,11 +781,6 @@ function KnowledgeSection({
|
||||||
onSelectFile: (path: string, kind: "file" | "dir") => void
|
onSelectFile: (path: string, kind: "file" | "dir") => void
|
||||||
actions: KnowledgeActions
|
actions: KnowledgeActions
|
||||||
onVoiceNoteCreated?: (path: string) => void
|
onVoiceNoteCreated?: (path: string) => void
|
||||||
searchOpen: boolean
|
|
||||||
setSearchOpen: (open: boolean) => void
|
|
||||||
searchQuery: string
|
|
||||||
setSearchQuery: (query: string) => void
|
|
||||||
searchInputRef: React.RefObject<HTMLInputElement | null>
|
|
||||||
}) {
|
}) {
|
||||||
const isExpanded = expandedPaths.size > 0
|
const isExpanded = expandedPaths.size > 0
|
||||||
|
|
||||||
|
|
@ -792,79 +794,38 @@ function KnowledgeSection({
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<ContextMenuTrigger asChild>
|
<ContextMenuTrigger asChild>
|
||||||
<SidebarGroup className="flex-1 flex flex-col overflow-hidden">
|
<SidebarGroup className="flex-1 flex flex-col overflow-hidden">
|
||||||
<div className="sticky top-0 z-10 bg-sidebar border-b border-sidebar-border">
|
<div className="flex items-center justify-center gap-1 py-1 sticky top-0 z-10 bg-sidebar border-b border-sidebar-border">
|
||||||
<div className="flex items-center justify-center gap-1 py-1">
|
{quickActions.map((action) => (
|
||||||
{quickActions.map((action) => (
|
<Tooltip key={action.label}>
|
||||||
<Tooltip key={action.label}>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<button
|
|
||||||
onClick={action.action}
|
|
||||||
className="text-sidebar-foreground/70 hover:text-sidebar-foreground hover:bg-sidebar-accent rounded p-1.5 transition-colors"
|
|
||||||
>
|
|
||||||
<action.icon className="size-4" />
|
|
||||||
</button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="bottom">{action.label}</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
))}
|
|
||||||
<VoiceNoteButton onNoteCreated={onVoiceNoteCreated} />
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<button
|
<button
|
||||||
onClick={isExpanded ? actions.collapseAll : actions.expandAll}
|
onClick={action.action}
|
||||||
className="text-sidebar-foreground/70 hover:text-sidebar-foreground hover:bg-sidebar-accent rounded p-1.5 transition-colors"
|
className="text-sidebar-foreground/70 hover:text-sidebar-foreground hover:bg-sidebar-accent rounded p-1.5 transition-colors"
|
||||||
>
|
>
|
||||||
{isExpanded ? (
|
<action.icon className="size-4" />
|
||||||
<ChevronsDownUp className="size-4" />
|
|
||||||
) : (
|
|
||||||
<ChevronsUpDown className="size-4" />
|
|
||||||
)}
|
|
||||||
</button>
|
</button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="bottom">
|
<TooltipContent side="bottom">{action.label}</TooltipContent>
|
||||||
{isExpanded ? "Collapse All" : "Expand All"}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip>
|
))}
|
||||||
<TooltipTrigger asChild>
|
<VoiceNoteButton onNoteCreated={onVoiceNoteCreated} />
|
||||||
<button
|
<Tooltip>
|
||||||
onClick={() => setSearchOpen(!searchOpen)}
|
<TooltipTrigger asChild>
|
||||||
className={cn(
|
<button
|
||||||
"rounded p-1.5 transition-colors",
|
onClick={isExpanded ? actions.collapseAll : actions.expandAll}
|
||||||
searchOpen
|
className="text-sidebar-foreground/70 hover:text-sidebar-foreground hover:bg-sidebar-accent rounded p-1.5 transition-colors"
|
||||||
? "text-sidebar-foreground bg-sidebar-accent"
|
>
|
||||||
: "text-sidebar-foreground/70 hover:text-sidebar-foreground hover:bg-sidebar-accent"
|
{isExpanded ? (
|
||||||
)}
|
<ChevronsDownUp className="size-4" />
|
||||||
>
|
) : (
|
||||||
<Search className="size-4" />
|
<ChevronsUpDown className="size-4" />
|
||||||
</button>
|
)}
|
||||||
</TooltipTrigger>
|
</button>
|
||||||
<TooltipContent side="bottom">Search</TooltipContent>
|
</TooltipTrigger>
|
||||||
</Tooltip>
|
<TooltipContent side="bottom">
|
||||||
</div>
|
{isExpanded ? "Collapse All" : "Expand All"}
|
||||||
{searchOpen && (
|
</TooltipContent>
|
||||||
<div className="flex items-center gap-1 rounded-md border border-sidebar-border bg-sidebar-accent/30 mx-2 mb-1 px-2 py-1">
|
</Tooltip>
|
||||||
<Search className="size-3.5 shrink-0 text-muted-foreground" />
|
|
||||||
<input
|
|
||||||
ref={searchInputRef}
|
|
||||||
value={searchQuery}
|
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "Escape") setSearchOpen(false)
|
|
||||||
}}
|
|
||||||
placeholder="Search files..."
|
|
||||||
className="flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground/60"
|
|
||||||
/>
|
|
||||||
{searchQuery && (
|
|
||||||
<button
|
|
||||||
onClick={() => setSearchQuery("")}
|
|
||||||
className="rounded p-0.5 text-muted-foreground hover:text-sidebar-foreground transition-colors"
|
|
||||||
>
|
|
||||||
<X className="size-3.5" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<SidebarGroupContent className="flex-1 overflow-y-auto">
|
<SidebarGroupContent className="flex-1 overflow-y-auto">
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
|
|
@ -1118,11 +1079,6 @@ function TasksSection({
|
||||||
actions,
|
actions,
|
||||||
backgroundTasks = [],
|
backgroundTasks = [],
|
||||||
selectedBackgroundTask,
|
selectedBackgroundTask,
|
||||||
searchOpen,
|
|
||||||
setSearchOpen,
|
|
||||||
searchQuery,
|
|
||||||
setSearchQuery,
|
|
||||||
searchInputRef,
|
|
||||||
}: {
|
}: {
|
||||||
runs: RunListItem[]
|
runs: RunListItem[]
|
||||||
currentRunId?: string | null
|
currentRunId?: string | null
|
||||||
|
|
@ -1130,65 +1086,19 @@ function TasksSection({
|
||||||
actions?: TasksActions
|
actions?: TasksActions
|
||||||
backgroundTasks?: BackgroundTaskItem[]
|
backgroundTasks?: BackgroundTaskItem[]
|
||||||
selectedBackgroundTask?: string | null
|
selectedBackgroundTask?: string | null
|
||||||
searchOpen: boolean
|
|
||||||
setSearchOpen: (open: boolean) => void
|
|
||||||
searchQuery: string
|
|
||||||
setSearchQuery: (query: string) => void
|
|
||||||
searchInputRef: React.RefObject<HTMLInputElement | null>
|
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<SidebarGroup className="flex-1 flex flex-col overflow-hidden">
|
<SidebarGroup className="flex-1 flex flex-col overflow-hidden">
|
||||||
{/* Sticky New Chat button + search */}
|
{/* Sticky New Chat button - matches Knowledge section height */}
|
||||||
<div className="sticky top-0 z-10 bg-sidebar border-b border-sidebar-border py-0.5">
|
<div className="sticky top-0 z-10 bg-sidebar border-b border-sidebar-border py-0.5">
|
||||||
<div className="flex items-center gap-1 px-1">
|
<SidebarMenu>
|
||||||
<SidebarMenu className="flex-1">
|
<SidebarMenuItem>
|
||||||
<SidebarMenuItem>
|
<SidebarMenuButton onClick={actions?.onNewChat} className="gap-2">
|
||||||
<SidebarMenuButton onClick={actions?.onNewChat} className="gap-2">
|
<SquarePen className="size-4 shrink-0" />
|
||||||
<SquarePen className="size-4 shrink-0" />
|
<span className="text-sm">New chat</span>
|
||||||
<span className="text-sm">New chat</span>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuItem>
|
||||||
</SidebarMenuItem>
|
</SidebarMenu>
|
||||||
</SidebarMenu>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<button
|
|
||||||
onClick={() => setSearchOpen(!searchOpen)}
|
|
||||||
className={cn(
|
|
||||||
"rounded p-1.5 transition-colors shrink-0",
|
|
||||||
searchOpen
|
|
||||||
? "text-sidebar-foreground bg-sidebar-accent"
|
|
||||||
: "text-sidebar-foreground/70 hover:text-sidebar-foreground hover:bg-sidebar-accent"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Search className="size-4" />
|
|
||||||
</button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="bottom">Search</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
{searchOpen && (
|
|
||||||
<div className="flex items-center gap-1 rounded-md border border-sidebar-border bg-sidebar-accent/30 mx-2 mb-1 mt-0.5 px-2 py-1">
|
|
||||||
<Search className="size-3.5 shrink-0 text-muted-foreground" />
|
|
||||||
<input
|
|
||||||
ref={searchInputRef}
|
|
||||||
value={searchQuery}
|
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "Escape") setSearchOpen(false)
|
|
||||||
}}
|
|
||||||
placeholder="Search chats..."
|
|
||||||
className="flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground/60"
|
|
||||||
/>
|
|
||||||
{searchQuery && (
|
|
||||||
<button
|
|
||||||
onClick={() => setSearchQuery("")}
|
|
||||||
className="rounded p-0.5 text-muted-foreground hover:text-sidebar-foreground transition-colors"
|
|
||||||
>
|
|
||||||
<X className="size-3.5" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<SidebarGroupContent className="flex-1 overflow-y-auto">
|
<SidebarGroupContent className="flex-1 overflow-y-auto">
|
||||||
{/* Background Tasks Section */}
|
{/* Background Tasks Section */}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ export type ActiveSection = "knowledge" | "tasks"
|
||||||
type SidebarSectionContextProps = {
|
type SidebarSectionContextProps = {
|
||||||
activeSection: ActiveSection
|
activeSection: ActiveSection
|
||||||
setActiveSection: (section: ActiveSection) => void
|
setActiveSection: (section: ActiveSection) => void
|
||||||
|
searchOpen: boolean
|
||||||
|
setSearchOpen: (open: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const SidebarSectionContext = React.createContext<SidebarSectionContextProps | null>(null)
|
const SidebarSectionContext = React.createContext<SidebarSectionContextProps | null>(null)
|
||||||
|
|
@ -29,6 +31,7 @@ export function SidebarSectionProvider({
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
const [activeSection, setActiveSectionState] = React.useState<ActiveSection>(defaultSection)
|
const [activeSection, setActiveSectionState] = React.useState<ActiveSection>(defaultSection)
|
||||||
|
const [searchOpen, setSearchOpen] = React.useState(false)
|
||||||
|
|
||||||
const setActiveSection = React.useCallback((section: ActiveSection) => {
|
const setActiveSection = React.useCallback((section: ActiveSection) => {
|
||||||
setActiveSectionState(section)
|
setActiveSectionState(section)
|
||||||
|
|
@ -39,8 +42,10 @@ export function SidebarSectionProvider({
|
||||||
() => ({
|
() => ({
|
||||||
activeSection,
|
activeSection,
|
||||||
setActiveSection,
|
setActiveSection,
|
||||||
|
searchOpen,
|
||||||
|
setSearchOpen,
|
||||||
}),
|
}),
|
||||||
[activeSection, setActiveSection]
|
[activeSection, setActiveSection, searchOpen]
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue