Skip to content

Commit ab3be4c

Browse files
committed
feat: add search history
1 parent 65458ea commit ab3be4c

File tree

8 files changed

+92
-26
lines changed

8 files changed

+92
-26
lines changed

bun.lockb

14.7 KB
Binary file not shown.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"@radix-ui/react-alert-dialog": "^1.0.5",
3434
"@radix-ui/react-context-menu": "^2.2.2",
3535
"@radix-ui/react-dialog": "^1.0.5",
36-
"@radix-ui/react-dropdown-menu": "^2.1.2",
36+
"@radix-ui/react-dropdown-menu": "^2.1.15",
3737
"@radix-ui/react-icons": "1.3.0",
3838
"@radix-ui/react-popover": "^1.0.7",
3939
"@radix-ui/react-portal": "^1.1.2",

src/components/databrowser/components/sidebar/index.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ import { KeysList } from "./keys-list"
1515
import { SearchInput } from "./search-input"
1616
import { LoadingSkeleton } from "./skeleton-buttons"
1717
import { DataTypeSelector } from "./type-selector"
18+
import {
19+
DropdownMenu,
20+
DropdownMenuContent,
21+
DropdownMenuTrigger,
22+
} from "@/components/ui/dropdown-menu"
1823

1924
export function Sidebar() {
2025
const { keys, query } = useKeys()

src/components/databrowser/components/sidebar/keys-list.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ const KeyItem = ({ data, nextKey }: { data: RedisKey; nextKey: string }) => {
4444
data-key={dataKey}
4545
variant={isKeySelected ? "default" : "ghost"}
4646
className={cn(
47-
"relative flex h-10 w-full items-center justify-start gap-2 px-3 py-0",
47+
"relative flex h-10 w-full items-center justify-start gap-2 px-3 py-0 !ring-0 focus-visible:bg-zinc-50",
4848
"select-none border border-transparent text-left",
4949
isKeySelected && "shadow-sm",
5050
isKeySelected && keyStyles[dataType]

src/components/databrowser/components/sidebar/search-input.tsx

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,84 @@ import { IconX } from "@tabler/icons-react"
44
import { Button } from "@/components/ui/button"
55
import { Input } from "@/components/ui/input"
66
import { useTab } from "@/tab-provider"
7+
import { useDatabrowserStore } from "@/store"
8+
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
9+
10+
const dedupeSearchHistory = (history: string[]) => {
11+
const seen = new Set()
12+
return history.filter((item) => {
13+
if (!item || seen.has(item)) return false
14+
seen.add(item)
15+
return true
16+
})
17+
}
718

819
export const SearchInput = () => {
920
const { setSearchKey, search } = useTab()
21+
const { searchHistory, addSearchHistory } = useDatabrowserStore()
1022
const [state, setState] = useState(search.key)
23+
const [isFocus, setIsFocus] = useState(false)
1124

12-
const submit = (value: string) => {
25+
const handleSubmit = (value: string) => {
1326
if (value.trim() !== "" && !value.includes("*")) value = `${value}*`
27+
addSearchHistory(value)
28+
setSearchKey(value)
29+
setState(value)
30+
}
31+
32+
const handleItemSelect = (value: string) => {
33+
addSearchHistory(value)
1434
setSearchKey(value)
1535
setState(value)
1636
}
1737

38+
const filteredHistory = dedupeSearchHistory(
39+
searchHistory.filter((item) => item.includes(state) && item !== state)
40+
).slice(0, 5)
41+
1842
return (
1943
<div className="relative grow">
20-
<Input
21-
placeholder="Search"
22-
className={"rounded-l-none border-zinc-300 font-normal"}
23-
onKeyDown={(e) => {
24-
if (e.key === "Enter") submit(e.currentTarget.value)
25-
}}
26-
onChange={(e) => {
27-
setState(e.currentTarget.value)
28-
if (e.currentTarget.value.trim() === "") submit("")
29-
}}
30-
value={state}
31-
/>
44+
<Popover open={isFocus && filteredHistory.length > 0}>
45+
<PopoverTrigger asChild>
46+
<div>
47+
<Input
48+
placeholder="Search"
49+
className={"rounded-l-none border-zinc-300 font-normal"}
50+
onKeyDown={(e) => {
51+
if (e.key === "Enter") handleSubmit(e.currentTarget.value)
52+
else if (e.key === "Escape") setState("")
53+
}}
54+
onChange={(e) => {
55+
setState(e.currentTarget.value)
56+
if (e.currentTarget.value.trim() === "") handleSubmit("")
57+
}}
58+
value={state}
59+
onFocus={() => setIsFocus(true)}
60+
onBlur={() => setIsFocus(false)}
61+
/>
62+
</div>
63+
</PopoverTrigger>
64+
65+
<PopoverContent
66+
className="w-[200px] divide-y px-3 py-2 text-[13px] text-zinc-900"
67+
autoFocus={false}
68+
onOpenAutoFocus={(e) => {
69+
e.preventDefault()
70+
e.stopPropagation()
71+
}}
72+
>
73+
{filteredHistory.map((item) => (
74+
<div key={item} className="w-full py-[2px]">
75+
<button
76+
onClick={() => handleItemSelect(item)}
77+
className="block w-full rounded-sm p-1 text-left transition-colors hover:bg-zinc-100"
78+
>
79+
{item}
80+
</button>
81+
</div>
82+
))}
83+
</PopoverContent>
84+
</Popover>
3285
{state && (
3386
<Button
3487
type="button"
@@ -43,7 +96,7 @@ export const SearchInput = () => {
4396
<IconX size={16} />
4497
<span className="sr-only">Clear</span>
4598
</Button>
46-
)}
99+
)}{" "}
47100
</div>
48101
)
49102
}

src/components/ui/button.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import { cn } from "@/lib/utils"
77
const buttonVariants = cva(
88
"inline-flex items-center justify-center rounded-md text-sm " +
99
"ring-offset-white transition-colors " +
10-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-950 focus-visible:ring-offset-2 " +
10+
// Disabled ring
11+
"focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-zinc-950 focus-visible:ring-offset-2 " +
1112
"disabled:pointer-events-none disabled:opacity-50 " +
1213
"dark:ring-offset-zinc-950 dark:focus-visible:ring-zinc-300",
1314
{

src/components/ui/dropdown-menu.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import * as React from "react"
22
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
3+
import { cn } from "@/lib/utils"
34
import { CheckIcon, ChevronRightIcon, DotFilledIcon } from "@radix-ui/react-icons"
4-
55
import { portalRoot } from "@/lib/portal-root"
6-
import { cn } from "@/lib/utils"
76

87
const DropdownMenu = DropdownMenuPrimitive.Root
98

@@ -26,14 +25,14 @@ const DropdownMenuSubTrigger = React.forwardRef<
2625
<DropdownMenuPrimitive.SubTrigger
2726
ref={ref}
2827
className={cn(
29-
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-zinc-100 data-[state=open]:bg-zinc-100 dark:focus:bg-zinc-800 dark:data-[state=open]:bg-zinc-800",
28+
"flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-neutral-100 data-[state=open]:bg-neutral-100 dark:focus:bg-neutral-800 dark:data-[state=open]:bg-neutral-800 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
3029
inset && "pl-8",
3130
className
3231
)}
3332
{...props}
3433
>
3534
{children}
36-
<ChevronRightIcon className="ml-auto size-4" />
35+
<ChevronRightIcon className="ml-auto" />
3736
</DropdownMenuPrimitive.SubTrigger>
3837
))
3938
DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName
@@ -45,7 +44,7 @@ const DropdownMenuSubContent = React.forwardRef<
4544
<DropdownMenuPrimitive.SubContent
4645
ref={ref}
4746
className={cn(
48-
"z-50 min-w-[8rem] overflow-hidden rounded-md border border-neutral-200 bg-white p-1 text-neutral-950 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
47+
"z-50 min-w-[8rem] origin-[--radix-dropdown-menu-content-transform-origin] overflow-hidden rounded-md border border-neutral-200 bg-white p-1 text-neutral-950 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
4948
className
5049
)}
5150
{...props}
@@ -62,8 +61,8 @@ const DropdownMenuContent = React.forwardRef<
6261
ref={ref}
6362
sideOffset={sideOffset}
6463
className={cn(
65-
"z-50 min-w-[8rem] overflow-hidden rounded-md border border-neutral-200 bg-white p-1 text-neutral-950 shadow-md dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
66-
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
64+
"z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border border-neutral-200 bg-white p-1 text-neutral-950 shadow-md dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
65+
"origin-[--radix-dropdown-menu-content-transform-origin] data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
6766
className
6867
)}
6968
{...props}
@@ -105,7 +104,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
105104
>
106105
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
107106
<DropdownMenuPrimitive.ItemIndicator>
108-
<CheckIcon className="size-4" />
107+
<CheckIcon className="h-4 w-4" />
109108
</DropdownMenuPrimitive.ItemIndicator>
110109
</span>
111110
{children}
@@ -127,7 +126,7 @@ const DropdownMenuRadioItem = React.forwardRef<
127126
>
128127
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
129128
<DropdownMenuPrimitive.ItemIndicator>
130-
<DotFilledIcon className="size-4 fill-current" />
129+
<DotFilledIcon className="h-2 w-2 fill-current" />
131130
</DropdownMenuPrimitive.ItemIndicator>
132131
</span>
133132
{children}

src/store.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ type DatabrowserStore = {
6666
setSearch: (tabId: TabId, search: SearchFilter) => void
6767
setSearchKey: (tabId: TabId, key: string) => void
6868
setSearchType: (tabId: TabId, type: DataType | undefined) => void
69+
70+
searchHistory: string[]
71+
addSearchHistory: (key: string) => void
6972
}
7073

7174
export type DatabrowserStoreObject = ReturnType<typeof createDatabrowserStore>
@@ -148,4 +151,9 @@ export const createDatabrowserStore = () =>
148151
[tabId]: { ...old.tabs[tabId], search: { ...old.tabs[tabId].search, type } },
149152
},
150153
})),
154+
155+
searchHistory: [],
156+
addSearchHistory: (key) => {
157+
set((old) => ({ ...old, searchHistory: [key, ...old.searchHistory] }))
158+
},
151159
}))

0 commit comments

Comments
 (0)