refactor(sidebar): consolidate search to omnibar, remove redundancy

Remove duplicate search inputs from Schema Explorer and Query History
since the omnibar already covers both. Fix omnibar spring bounce easing
to smooth expo ease-out under 200ms. Consolidate Quick Query's identical
dual buttons into one, remove its Recent section that duplicated History.
This commit is contained in:
Rohith Gilla 2026-04-17 09:16:48 +05:30
parent e9787e7218
commit 0054dce5a7
No known key found for this signature in database
4 changed files with 24 additions and 195 deletions

View file

@ -1,12 +1,11 @@
import { useState, useMemo } from 'react'
import { ChevronRight, Clock, Copy, MoreHorizontal, Play, Trash2, Search, X } from 'lucide-react'
import { ChevronRight, Clock, Copy, MoreHorizontal, Play, Trash2 } from 'lucide-react'
import { Badge, Input, Collapsible, CollapsibleContent, CollapsibleTrigger, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, useSidebar } from '@data-peek/ui'
import { Badge, Collapsible, CollapsibleContent, CollapsibleTrigger, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, SidebarGroup, SidebarGroupAction, SidebarGroupContent, SidebarGroupLabel, SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, useSidebar } from '@data-peek/ui'
import { useQueryStore, useConnectionStore, useTabStore } from '@/stores'
import { QueryHistoryDialog } from './query-history-dialog'
import {
filterHistory,
formatRelativeTime,
truncateQuery,
getQueryType,
@ -25,27 +24,14 @@ export function QueryHistory() {
const createQueryTab = useTabStore((s) => s.createQueryTab)
const [isHistoryDialogOpen, setIsHistoryDialogOpen] = useState(false)
const [isExpanded, setIsExpanded] = useState(false)
const [searchQuery, setSearchQuery] = useState('')
const filteredHistory = useMemo(() => {
const connectionFiltered = activeConnectionId
return activeConnectionId
? history.filter((h) => h.connectionId === activeConnectionId || !h.connectionId)
: history
}, [history, activeConnectionId])
if (!searchQuery.trim()) {
return connectionFiltered
}
return filterHistory(connectionFiltered, {
searchQuery,
filterStatus: 'all',
filterType: 'all',
connectionId: null
})
}, [history, activeConnectionId, searchQuery])
const displayLimit = searchQuery.trim() ? 20 : 10
const displayedHistory = filteredHistory.slice(0, displayLimit)
const displayedHistory = filteredHistory.slice(0, 10)
const handleQueryClick = (query: string) => {
const activeTab = getActiveTab()
@ -87,35 +73,10 @@ export function QueryHistory() {
</SidebarGroupLabel>
<CollapsibleContent>
<SidebarGroupContent>
<div className="px-2 pb-2">
<div className="relative">
<Search className="absolute left-2 top-1/2 -translate-y-1/2 size-3 text-muted-foreground" />
<Input
type="text"
placeholder="Search history..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="h-7 pl-7 pr-7 text-xs"
/>
{searchQuery && (
<button
onClick={() => setSearchQuery('')}
className="absolute right-1.5 top-1/2 -translate-y-1/2 size-4 flex items-center justify-center text-muted-foreground hover:text-foreground"
>
<X className="size-3" />
</button>
)}
</div>
</div>
<SidebarMenu>
{displayedHistory.length === 0 ? (
<div className="px-2 py-4 text-xs text-muted-foreground text-center">
{searchQuery
? 'No matching queries'
: activeConnectionId
? 'No queries yet'
: 'Select a connection'}
{activeConnectionId ? 'No queries yet' : 'Select a connection'}
</div>
) : (
displayedHistory.map((item) => {
@ -201,7 +162,7 @@ export function QueryHistory() {
)
})
)}
{filteredHistory.length > displayLimit && (
{filteredHistory.length > 10 && (
<SidebarMenuItem>
<SidebarMenuButton
className="text-sidebar-foreground/70"

View file

@ -9,7 +9,7 @@ import {
Database as SchemaIcon,
Loader2,
XCircle,
Search,
X,
Network,
Plus,
@ -41,7 +41,7 @@ import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
Input,
Tooltip,
TooltipContent,
TooltipProvider,
@ -529,8 +529,6 @@ export function SchemaExplorer() {
)
const [expandedTables, setExpandedTables] = React.useState<Set<string>>(new Set())
const [expandedRoutines, setExpandedRoutines] = React.useState<Set<string>>(new Set())
const [searchQuery, setSearchQuery] = React.useState('')
// Only animate stagger on schema data changes, not on every re-render
const animatedSchemasRef = React.useRef<typeof schemas>(null)
const shouldAnimateStagger = animatedSchemasRef.current !== schemas
@ -550,45 +548,23 @@ export function SchemaExplorer() {
const createQueryTab = useTabStore((s) => s.createQueryTab)
// Filter schemas and tables/routines based on search query, filter toggles, and focused schema
const filteredSchemas = React.useMemo(() => {
const query = searchQuery.toLowerCase().trim()
return schemas
.filter((schema) => {
// If a schema is focused, only show that schema
if (focusedSchema && schema.name !== focusedSchema) return false
return true
})
.map((schema) => {
// Filter tables based on type and search
const filteredTables = schema.tables.filter((table) => {
// Type filter
if (table.type === 'table' && !showTables) return false
if (table.type === 'view' && !showViews) return false
if (table.type === 'materialized_view' && !showMaterializedViews) return false
// Search filter - match table name or column names
if (
query &&
!table.name.toLowerCase().includes(query) &&
!table.columns.some((c) => c.name.toLowerCase().includes(query))
)
return false
return true
})
// Filter routines based on type and search
const filteredRoutines = schema.routines?.filter((routine) => {
// Type filter
if (routine.type === 'function' && !showFunctions) return false
if (routine.type === 'procedure' && !showProcedures) return false
// Search filter - match routine name or parameter names
if (
query &&
!routine.name.toLowerCase().includes(query) &&
!routine.parameters.some((p) => p.name.toLowerCase().includes(query))
)
return false
return true
})
@ -601,7 +577,6 @@ export function SchemaExplorer() {
.filter((schema) => schema.tables.length > 0 || (schema.routines?.length ?? 0) > 0)
}, [
schemas,
searchQuery,
showTables,
showViews,
showMaterializedViews,
@ -610,13 +585,6 @@ export function SchemaExplorer() {
focusedSchema
])
// Auto-expand schemas when searching
React.useEffect(() => {
if (searchQuery.trim()) {
setExpandedSchemas(new Set(filteredSchemas.map((s) => s.name)))
}
}, [searchQuery, filteredSchemas])
// Update expanded schemas when schemas change
React.useEffect(() => {
setExpandedSchemas(new Set(schemas.map((s) => s.name)))
@ -1081,35 +1049,10 @@ export function SchemaExplorer() {
</div>
</div>
)}
{/* Search Input */}
<div className="px-2 pb-2">
<div className="relative">
<Search className="absolute left-2 top-1/2 -translate-y-1/2 size-3.5 text-muted-foreground" />
<Input
type="text"
placeholder="Search tables, columns, routines..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="h-7 pl-7 pr-7 text-xs"
/>
{searchQuery && (
<Button
variant="ghost"
size="icon"
className="absolute right-0.5 top-1/2 -translate-y-1/2 size-6 hover:bg-transparent"
onClick={() => setSearchQuery('')}
>
<X className="size-3.5 text-muted-foreground" />
</Button>
)}
</div>
</div>
<SidebarMenu>
{filteredSchemas.length === 0 ? (
<div className="px-2 py-4 text-xs text-muted-foreground text-center">
{searchQuery
? 'No tables, columns, or routines match your search'
: 'No schemas found'}
No schemas found
</div>
) : (
filteredSchemas.map((schema) => (

View file

@ -339,7 +339,7 @@ export function SidebarOmnibar() {
</div>
<div
className="overflow-hidden transition-all duration-300 ease-[cubic-bezier(0.34,1.3,0.64,1)]"
className="overflow-hidden transition-all duration-150 ease-[cubic-bezier(0.16,1,0.3,1)]"
style={{
display: 'grid',
gridTemplateRows: isActive && query.length > 0 ? '1fr' : '0fr'

View file

@ -1,9 +1,9 @@
import * as React from 'react'
import { Play, Send, ChevronDown, Clock } from 'lucide-react'
import { Button, Collapsible, CollapsibleContent, CollapsibleTrigger, cn, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '@data-peek/ui'
import { Play, ChevronDown } from 'lucide-react'
import { Button, Collapsible, CollapsibleContent, CollapsibleTrigger, cn, SidebarGroup, SidebarGroupContent, SidebarGroupLabel } from '@data-peek/ui'
import { SQLEditor } from '@/components/sql-editor'
import { useQueryStore, useConnectionStore, useTabStore, useSettingsStore } from '@/stores'
import { useConnectionStore, useTabStore, useSettingsStore } from '@/stores'
export function SidebarQuickQuery() {
const hideQuickQueryPanel = useSettingsStore((s) => s.hideQuickQueryPanel)
@ -13,44 +13,17 @@ export function SidebarQuickQuery() {
const activeConnection = useConnectionStore((s) => s.getActiveConnection())
const schemas = useConnectionStore((s) => s.schemas)
const { history } = useQueryStore()
const createQueryTab = useTabStore((s) => s.createQueryTab)
// Get recent 3 queries for quick access
const recentQueries = React.useMemo(() => {
return history
.filter((h) => h.status === 'success')
.slice(0, 3)
.map((h) => ({
id: h.id,
query: h.query,
preview: h.query.replace(/\s+/g, ' ').slice(0, 40) + (h.query.length > 40 ? '...' : '')
}))
}, [history])
const handleRunQuickQuery = () => {
if (!activeConnection || !quickQuery.trim()) return
// Create a new query tab with the query
createQueryTab(activeConnection.id, quickQuery)
setQuickQuery('')
}
const handleSendToMainEditor = () => {
if (!quickQuery.trim() || !activeConnection) return
// Create a new query tab with the query
createQueryTab(activeConnection.id, quickQuery)
setQuickQuery('')
}
const handleUseRecentQuery = (query: string) => {
if (!activeConnection) return
// Create a new query tab with the recent query
createQueryTab(activeConnection.id, query)
}
if (hideQuickQueryPanel) {
return <></>
return null
}
return (
@ -84,64 +57,16 @@ export function SidebarQuickQuery() {
schemas={schemas}
/>
{/* Action Buttons */}
<div className="flex gap-1.5">
<Button
size="sm"
className="flex-1 h-7 gap-1.5 text-xs"
disabled={!activeConnection || !quickQuery.trim()}
onClick={handleRunQuickQuery}
>
<Play className="size-3" />
New Tab
</Button>
<Button
variant="outline"
size="sm"
className="flex-1 h-7 gap-1.5 text-xs"
disabled={!quickQuery.trim()}
onClick={handleSendToMainEditor}
>
<Send className="size-3" />
Send to Editor
</Button>
</div>
<Button
size="sm"
className="h-7 gap-1.5 text-xs"
disabled={!activeConnection || !quickQuery.trim()}
onClick={handleRunQuickQuery}
>
<Play className="size-3" />
Run in New Tab
</Button>
</div>
{/* Recent Queries */}
{recentQueries.length > 0 && (
<div className="mt-3 pt-3 border-t border-border/40">
<div className="flex items-center gap-1.5 mb-2">
<Clock className="size-3 text-muted-foreground" />
<span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
Recent
</span>
</div>
<SidebarMenu>
{recentQueries.map((item) => (
<SidebarMenuItem key={item.id}>
<SidebarMenuButton
onClick={() => handleUseRecentQuery(item.query)}
className="h-auto py-1.5 px-2"
>
<code className="text-[10px] font-mono text-muted-foreground truncate">
{item.preview}
</code>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</div>
)}
{/* No Connection State */}
{!activeConnection && (
<div className="mt-2 text-center">
<p className="text-[10px] text-muted-foreground">
Select a connection to run queries
</p>
</div>
)}
</SidebarGroupContent>
</CollapsibleContent>
</SidebarGroup>