chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1 @@
|
||||
export { useLogDetailsResize } from './use-log-details-resize'
|
||||
@@ -0,0 +1,52 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useLogDetailsUIStore } from '@/stores/logs/store'
|
||||
|
||||
/**
|
||||
* Hook for handling log details panel resize via mouse drag.
|
||||
* @returns Resize state and mouse event handler.
|
||||
*/
|
||||
export function useLogDetailsResize() {
|
||||
const setPanelWidth = useLogDetailsUIStore((state) => state.setPanelWidth)
|
||||
const setIsResizing = useLogDetailsUIStore((state) => state.setIsResizing)
|
||||
const [isResizing, setLocalIsResizing] = useState(false)
|
||||
|
||||
const handleMouseDown = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
e.preventDefault()
|
||||
setLocalIsResizing(true)
|
||||
setIsResizing(true)
|
||||
},
|
||||
[setIsResizing]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isResizing) return
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
const newWidth = window.innerWidth - e.clientX
|
||||
setPanelWidth(newWidth)
|
||||
}
|
||||
|
||||
const handleMouseUp = () => {
|
||||
setLocalIsResizing(false)
|
||||
setIsResizing(false)
|
||||
}
|
||||
|
||||
document.addEventListener('mousemove', handleMouseMove)
|
||||
document.addEventListener('mouseup', handleMouseUp)
|
||||
document.body.style.cursor = 'ew-resize'
|
||||
document.body.style.userSelect = 'none'
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousemove', handleMouseMove)
|
||||
document.removeEventListener('mouseup', handleMouseUp)
|
||||
document.body.style.cursor = ''
|
||||
document.body.style.userSelect = ''
|
||||
}
|
||||
}, [isResizing, setPanelWidth, setIsResizing])
|
||||
|
||||
return {
|
||||
isResizing,
|
||||
handleMouseDown,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { debounce, useQueryStates } from 'nuqs'
|
||||
import {
|
||||
logFilterParsers,
|
||||
logFilterUrlKeys,
|
||||
} from '@/app/workspace/[workspaceId]/logs/search-params'
|
||||
import type { LogLevel, TimeRange, TriggerType } from '@/stores/logs/filters/types'
|
||||
|
||||
const DEFAULT_TIME_RANGE: TimeRange = 'All time'
|
||||
|
||||
/** Debounce window for `search` URL writes; keystrokes stay instant in the input. */
|
||||
const SEARCH_DEBOUNCE_MS = 300 as const
|
||||
|
||||
/**
|
||||
* The logs filter state, sourced entirely from typed URL query params via nuqs.
|
||||
*
|
||||
* This replaces the former hand-rolled `useFilterStore` URL sync (`syncWithURL`
|
||||
* / `initializeFromURL` / `popstate`). The URL is now the single source of
|
||||
* truth — initialization, serialization, and back/forward navigation are all
|
||||
* handled by nuqs. The action surface mirrors the previous store so consumers
|
||||
* migrate with minimal churn.
|
||||
*/
|
||||
export interface UseLogFilters {
|
||||
timeRange: TimeRange
|
||||
startDate: string | undefined
|
||||
endDate: string | undefined
|
||||
level: LogLevel
|
||||
workflowIds: string[]
|
||||
folderIds: string[]
|
||||
triggers: TriggerType[]
|
||||
searchQuery: string
|
||||
|
||||
setTimeRange: (timeRange: TimeRange) => void
|
||||
setDateRange: (startDate: string | undefined, endDate: string | undefined) => void
|
||||
clearDateRange: () => void
|
||||
setLevel: (level: LogLevel) => void
|
||||
setWorkflowIds: (workflowIds: string[]) => void
|
||||
toggleWorkflowId: (workflowId: string) => void
|
||||
setFolderIds: (folderIds: string[]) => void
|
||||
toggleFolderId: (folderId: string) => void
|
||||
setSearchQuery: (query: string) => void
|
||||
setTriggers: (triggers: TriggerType[]) => void
|
||||
toggleTrigger: (trigger: TriggerType) => void
|
||||
resetFilters: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook exposing the logs filter state and actions backed by URL query params.
|
||||
* `startDate`/`endDate` are only retained while the time range is "Custom range"
|
||||
* to match the prior store semantics.
|
||||
*/
|
||||
export function useLogFilters(): UseLogFilters {
|
||||
const [filters, setFilters] = useQueryStates(logFilterParsers, logFilterUrlKeys)
|
||||
|
||||
const setTimeRange = useCallback(
|
||||
(timeRange: TimeRange) => {
|
||||
if (timeRange === 'Custom range') {
|
||||
setFilters({ timeRange })
|
||||
} else {
|
||||
setFilters({ timeRange, startDate: null, endDate: null })
|
||||
}
|
||||
},
|
||||
[setFilters]
|
||||
)
|
||||
|
||||
const setDateRange = useCallback(
|
||||
(startDate: string | undefined, endDate: string | undefined) => {
|
||||
setFilters({
|
||||
timeRange: 'Custom range',
|
||||
startDate: startDate ?? null,
|
||||
endDate: endDate ?? null,
|
||||
})
|
||||
},
|
||||
[setFilters]
|
||||
)
|
||||
|
||||
const clearDateRange = useCallback(() => {
|
||||
setFilters({ timeRange: DEFAULT_TIME_RANGE, startDate: null, endDate: null })
|
||||
}, [setFilters])
|
||||
|
||||
const setLevel = useCallback((level: LogLevel) => setFilters({ level }), [setFilters])
|
||||
|
||||
const setWorkflowIds = useCallback(
|
||||
(workflowIds: string[]) => setFilters({ workflowIds }),
|
||||
[setFilters]
|
||||
)
|
||||
|
||||
const toggleWorkflowId = useCallback(
|
||||
(workflowId: string) => {
|
||||
setFilters((prev) => {
|
||||
const current = prev.workflowIds
|
||||
const next = current.includes(workflowId)
|
||||
? current.filter((id) => id !== workflowId)
|
||||
: [...current, workflowId]
|
||||
return { workflowIds: next }
|
||||
})
|
||||
},
|
||||
[setFilters]
|
||||
)
|
||||
|
||||
const setFolderIds = useCallback((folderIds: string[]) => setFilters({ folderIds }), [setFilters])
|
||||
|
||||
const toggleFolderId = useCallback(
|
||||
(folderId: string) => {
|
||||
setFilters((prev) => {
|
||||
const current = prev.folderIds
|
||||
const next = current.includes(folderId)
|
||||
? current.filter((id) => id !== folderId)
|
||||
: [...current, folderId]
|
||||
return { folderIds: next }
|
||||
})
|
||||
},
|
||||
[setFilters]
|
||||
)
|
||||
|
||||
/**
|
||||
* Debounces only the search param's URL write; the returned `filters.search`
|
||||
* value still updates instantly so the controlled input stays responsive.
|
||||
* Clearing flushes immediately so the param drops out without lingering.
|
||||
*/
|
||||
const setSearchQuery = useCallback(
|
||||
(query: string) => {
|
||||
const trimmed = query.trim()
|
||||
const next = trimmed.length > 0 ? trimmed : null
|
||||
setFilters(
|
||||
{ search: next },
|
||||
next === null ? undefined : { limitUrlUpdates: debounce(SEARCH_DEBOUNCE_MS) }
|
||||
)
|
||||
},
|
||||
[setFilters]
|
||||
)
|
||||
|
||||
const setTriggers = useCallback(
|
||||
(triggers: TriggerType[]) => setFilters({ triggers }),
|
||||
[setFilters]
|
||||
)
|
||||
|
||||
const toggleTrigger = useCallback(
|
||||
(trigger: TriggerType) => {
|
||||
setFilters((prev) => {
|
||||
const current = prev.triggers
|
||||
const next = current.includes(trigger)
|
||||
? current.filter((t) => t !== trigger)
|
||||
: [...current, trigger]
|
||||
return { triggers: next }
|
||||
})
|
||||
},
|
||||
[setFilters]
|
||||
)
|
||||
|
||||
const resetFilters = useCallback(() => {
|
||||
setFilters({
|
||||
timeRange: DEFAULT_TIME_RANGE,
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
level: 'all',
|
||||
workflowIds: [],
|
||||
folderIds: [],
|
||||
triggers: [],
|
||||
search: null,
|
||||
})
|
||||
}, [setFilters])
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
timeRange: filters.timeRange,
|
||||
startDate:
|
||||
filters.timeRange === 'Custom range' ? (filters.startDate ?? undefined) : undefined,
|
||||
endDate: filters.timeRange === 'Custom range' ? (filters.endDate ?? undefined) : undefined,
|
||||
level: filters.level,
|
||||
workflowIds: filters.workflowIds,
|
||||
folderIds: filters.folderIds,
|
||||
triggers: filters.triggers,
|
||||
searchQuery: filters.search,
|
||||
setTimeRange,
|
||||
setDateRange,
|
||||
clearDateRange,
|
||||
setLevel,
|
||||
setWorkflowIds,
|
||||
toggleWorkflowId,
|
||||
setFolderIds,
|
||||
toggleFolderId,
|
||||
setSearchQuery,
|
||||
setTriggers,
|
||||
toggleTrigger,
|
||||
resetFilters,
|
||||
}),
|
||||
[
|
||||
filters,
|
||||
setTimeRange,
|
||||
setDateRange,
|
||||
clearDateRange,
|
||||
setLevel,
|
||||
setWorkflowIds,
|
||||
toggleWorkflowId,
|
||||
setFolderIds,
|
||||
toggleFolderId,
|
||||
setSearchQuery,
|
||||
setTriggers,
|
||||
toggleTrigger,
|
||||
resetFilters,
|
||||
]
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import type { ParsedFilter } from '@/lib/logs/query-parser'
|
||||
import { parseQuery } from '@/lib/logs/query-parser'
|
||||
import type {
|
||||
Suggestion,
|
||||
SuggestionGroup,
|
||||
SuggestionSection,
|
||||
} from '@/app/workspace/[workspaceId]/logs/types'
|
||||
|
||||
interface UseSearchStateOptions {
|
||||
onFiltersChange: (filters: ParsedFilter[], textSearch: string) => void
|
||||
getSuggestions: (input: string) => SuggestionGroup | null
|
||||
debounceMs?: number
|
||||
initialQuery?: string
|
||||
}
|
||||
|
||||
export function useSearchState({
|
||||
onFiltersChange,
|
||||
getSuggestions,
|
||||
debounceMs = 100,
|
||||
initialQuery,
|
||||
}: UseSearchStateOptions) {
|
||||
const [initialParsed] = useState(() =>
|
||||
initialQuery ? parseQuery(initialQuery) : { filters: [] as ParsedFilter[], textSearch: '' }
|
||||
)
|
||||
const [appliedFilters, setAppliedFilters] = useState<ParsedFilter[]>(initialParsed.filters)
|
||||
const [currentInput, setCurrentInput] = useState('')
|
||||
const [textSearch, setTextSearch] = useState<string>(initialParsed.textSearch)
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [suggestions, setSuggestions] = useState<Suggestion[]>([])
|
||||
const [sections, setSections] = useState<SuggestionSection[]>([])
|
||||
const [highlightedIndex, setHighlightedIndex] = useState(-1)
|
||||
|
||||
const [highlightedBadgeIndex, setHighlightedBadgeIndex] = useState<number | null>(null)
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
const debounceRef = useRef<NodeJS.Timeout | null>(null)
|
||||
|
||||
const updateSuggestions = useCallback(
|
||||
(input: string) => {
|
||||
const suggestionGroup = getSuggestions(input)
|
||||
|
||||
if (suggestionGroup && suggestionGroup.suggestions.length > 0) {
|
||||
setSuggestions(suggestionGroup.suggestions)
|
||||
setSections(suggestionGroup.sections || [])
|
||||
setIsOpen(true)
|
||||
setHighlightedIndex(0)
|
||||
} else {
|
||||
setIsOpen(false)
|
||||
setSuggestions([])
|
||||
setSections([])
|
||||
setHighlightedIndex(-1)
|
||||
}
|
||||
},
|
||||
[getSuggestions]
|
||||
)
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
(value: string) => {
|
||||
setCurrentInput(value)
|
||||
setHighlightedBadgeIndex(null)
|
||||
|
||||
if (debounceRef.current) {
|
||||
clearTimeout(debounceRef.current)
|
||||
}
|
||||
|
||||
debounceRef.current = setTimeout(() => {
|
||||
updateSuggestions(value)
|
||||
}, debounceMs)
|
||||
},
|
||||
[updateSuggestions, debounceMs]
|
||||
)
|
||||
|
||||
const handleSuggestionSelect = useCallback(
|
||||
(suggestion: Suggestion) => {
|
||||
if (suggestion.category === 'show-all') {
|
||||
setTextSearch(suggestion.value)
|
||||
setCurrentInput('')
|
||||
setIsOpen(false)
|
||||
onFiltersChange(appliedFilters, suggestion.value)
|
||||
return
|
||||
}
|
||||
|
||||
if (suggestion.category === 'filters' && suggestion.value.endsWith(':')) {
|
||||
setCurrentInput(suggestion.value)
|
||||
updateSuggestions(suggestion.value)
|
||||
return
|
||||
}
|
||||
|
||||
const newFilter: ParsedFilter = {
|
||||
field: suggestion.value.split(':')[0],
|
||||
operator: '=',
|
||||
value: suggestion.value.includes(':')
|
||||
? suggestion.value.split(':').slice(1).join(':').replace(/"/g, '')
|
||||
: suggestion.value.replace(/"/g, ''),
|
||||
originalValue: suggestion.value.includes(':')
|
||||
? suggestion.value.split(':').slice(1).join(':')
|
||||
: suggestion.value,
|
||||
}
|
||||
|
||||
const updatedFilters = [...appliedFilters, newFilter]
|
||||
setAppliedFilters(updatedFilters)
|
||||
setCurrentInput('')
|
||||
setTextSearch('')
|
||||
|
||||
onFiltersChange(updatedFilters, '')
|
||||
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus()
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
updateSuggestions('')
|
||||
}, 50)
|
||||
},
|
||||
[appliedFilters, onFiltersChange, updateSuggestions]
|
||||
)
|
||||
|
||||
const removeBadge = useCallback(
|
||||
(index: number) => {
|
||||
const updatedFilters = appliedFilters.filter((_, i) => i !== index)
|
||||
setAppliedFilters(updatedFilters)
|
||||
onFiltersChange(updatedFilters, textSearch)
|
||||
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus()
|
||||
}
|
||||
},
|
||||
[appliedFilters, textSearch, onFiltersChange]
|
||||
)
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(event: React.KeyboardEvent) => {
|
||||
if (event.key === 'Backspace' && currentInput === '') {
|
||||
event.preventDefault()
|
||||
|
||||
if (highlightedBadgeIndex !== null) {
|
||||
removeBadge(highlightedBadgeIndex)
|
||||
setHighlightedBadgeIndex(null)
|
||||
} else if (appliedFilters.length > 0) {
|
||||
setHighlightedBadgeIndex(appliedFilters.length - 1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
highlightedBadgeIndex !== null &&
|
||||
!['ArrowDown', 'ArrowUp', 'Enter'].includes(event.key)
|
||||
) {
|
||||
setHighlightedBadgeIndex(null)
|
||||
}
|
||||
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
|
||||
if (isOpen && highlightedIndex >= 0 && suggestions[highlightedIndex]) {
|
||||
handleSuggestionSelect(suggestions[highlightedIndex])
|
||||
} else if (currentInput.trim()) {
|
||||
setTextSearch(currentInput.trim())
|
||||
setCurrentInput('')
|
||||
setIsOpen(false)
|
||||
onFiltersChange(appliedFilters, currentInput.trim())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (!isOpen) return
|
||||
|
||||
switch (event.key) {
|
||||
case 'ArrowDown': {
|
||||
event.preventDefault()
|
||||
setHighlightedIndex((prev) => Math.min(prev + 1, suggestions.length - 1))
|
||||
break
|
||||
}
|
||||
|
||||
case 'ArrowUp': {
|
||||
event.preventDefault()
|
||||
setHighlightedIndex((prev) => Math.max(prev - 1, 0))
|
||||
break
|
||||
}
|
||||
|
||||
case 'Escape': {
|
||||
event.preventDefault()
|
||||
setIsOpen(false)
|
||||
setHighlightedIndex(-1)
|
||||
break
|
||||
}
|
||||
|
||||
case 'Tab': {
|
||||
if (highlightedIndex >= 0 && suggestions[highlightedIndex]) {
|
||||
event.preventDefault()
|
||||
handleSuggestionSelect(suggestions[highlightedIndex])
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
currentInput,
|
||||
appliedFilters,
|
||||
highlightedBadgeIndex,
|
||||
isOpen,
|
||||
highlightedIndex,
|
||||
suggestions,
|
||||
handleSuggestionSelect,
|
||||
removeBadge,
|
||||
onFiltersChange,
|
||||
]
|
||||
)
|
||||
|
||||
const handleFocus = useCallback(() => {
|
||||
updateSuggestions(currentInput)
|
||||
}, [currentInput, updateSuggestions])
|
||||
|
||||
const handleBlur = useCallback(() => {
|
||||
setTimeout(() => {
|
||||
setIsOpen(false)
|
||||
setHighlightedIndex(-1)
|
||||
}, 150)
|
||||
}, [])
|
||||
|
||||
const clearAll = useCallback(() => {
|
||||
setAppliedFilters([])
|
||||
setCurrentInput('')
|
||||
setTextSearch('')
|
||||
setIsOpen(false)
|
||||
onFiltersChange([], '')
|
||||
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus()
|
||||
}
|
||||
}, [onFiltersChange])
|
||||
|
||||
const initializeFromQuery = useCallback((query: string, filters: ParsedFilter[]) => {
|
||||
setAppliedFilters(filters)
|
||||
setTextSearch(query)
|
||||
setCurrentInput('')
|
||||
}, [])
|
||||
|
||||
return {
|
||||
appliedFilters,
|
||||
currentInput,
|
||||
textSearch,
|
||||
isOpen,
|
||||
suggestions,
|
||||
sections,
|
||||
highlightedIndex,
|
||||
highlightedBadgeIndex,
|
||||
|
||||
inputRef,
|
||||
dropdownRef,
|
||||
|
||||
handleInputChange,
|
||||
handleSuggestionSelect,
|
||||
handleKeyDown,
|
||||
handleFocus,
|
||||
handleBlur,
|
||||
removeBadge,
|
||||
clearAll,
|
||||
initializeFromQuery,
|
||||
setHighlightedIndex,
|
||||
setHighlightedBadgeIndex,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user