refactor(search): extract search into workspace package
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
import * as React from "react"
|
||||
|
||||
interface UseSearchHistoryOptions {
|
||||
limit: number
|
||||
storageKey?: string
|
||||
}
|
||||
|
||||
export function useSearchHistory({
|
||||
limit,
|
||||
storageKey,
|
||||
}: UseSearchHistoryOptions) {
|
||||
const [items, setItems] = React.useState<readonly string[]>(() =>
|
||||
readSearchHistory(storageKey, limit)
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
const syncFromStorage = () => setItems(readSearchHistory(storageKey, limit))
|
||||
const handleStorage = (event: StorageEvent) => {
|
||||
if (event.key === storageKey) syncFromStorage()
|
||||
}
|
||||
|
||||
syncFromStorage()
|
||||
if (!storageKey || typeof window === "undefined") return
|
||||
|
||||
window.addEventListener("storage", handleStorage)
|
||||
return () => window.removeEventListener("storage", handleStorage)
|
||||
}, [limit, storageKey])
|
||||
|
||||
const update = React.useCallback(
|
||||
(updater: (current: readonly string[]) => readonly string[]) => {
|
||||
setItems((current) => {
|
||||
const next = updater(current).slice(0, limit)
|
||||
writeSearchHistory(storageKey, next)
|
||||
return next
|
||||
})
|
||||
},
|
||||
[limit, storageKey]
|
||||
)
|
||||
|
||||
const add = React.useCallback(
|
||||
(query: string) => {
|
||||
const normalizedQuery = query.trim()
|
||||
if (!normalizedQuery) return
|
||||
|
||||
update((current) => [
|
||||
normalizedQuery,
|
||||
...current.filter((item) => item !== normalizedQuery),
|
||||
])
|
||||
},
|
||||
[update]
|
||||
)
|
||||
|
||||
const remove = React.useCallback(
|
||||
(query: string) =>
|
||||
update((current) => current.filter((item) => item !== query)),
|
||||
[update]
|
||||
)
|
||||
const clear = React.useCallback(() => update(() => []), [update])
|
||||
|
||||
return { add, clear, items, remove }
|
||||
}
|
||||
|
||||
function readSearchHistory(storageKey: string | undefined, limit: number) {
|
||||
if (!storageKey || typeof window === "undefined") return []
|
||||
|
||||
try {
|
||||
const stored = window.localStorage.getItem(storageKey)
|
||||
if (!stored) return []
|
||||
|
||||
const parsed: unknown = JSON.parse(stored)
|
||||
if (!Array.isArray(parsed)) return []
|
||||
|
||||
return Array.from(
|
||||
new Set(
|
||||
parsed
|
||||
.filter((item): item is string => typeof item === "string")
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean)
|
||||
)
|
||||
).slice(0, limit)
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function writeSearchHistory(
|
||||
storageKey: string | undefined,
|
||||
items: readonly string[]
|
||||
) {
|
||||
if (!storageKey || typeof window === "undefined") return
|
||||
|
||||
try {
|
||||
if (items.length === 0) {
|
||||
window.localStorage.removeItem(storageKey)
|
||||
} else {
|
||||
window.localStorage.setItem(storageKey, JSON.stringify(items))
|
||||
}
|
||||
} catch {
|
||||
// Storage can be disabled or unavailable in private browsing contexts.
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user