ultisuite-client/components/drive/drive-search-bar.tsx
R3D347HR4Y ad1370ea7e
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
feat: enhance configuration and add new demo layouts
- Introduced turbopack alias for canvas in next.config.mjs.
- Updated package.json scripts for development and branding tasks.
- Added new dependencies for Tiptap extensions.
- Implemented new demo layouts for agenda, contacts, drive, and mail applications.
- Enhanced globals.css for improved theming and splash screen animations.
- Added OAuth callback handling for drive mounts.
- Updated layout components to integrate new demo shells and improve structure.
2026-06-12 19:10:24 +02:00

170 lines
5.0 KiB
TypeScript

"use client"
import { useRef, useState } from "react"
import { useRouter } from "next/navigation"
import { Search, X } from "lucide-react"
import { DriveSearchSuggestionsPanel } from "@/components/drive/drive-search-suggestions"
import { Button } from "@/components/ui/button"
import type { DriveFileInfo } from "@/lib/api/types"
import { useDriveSearchSuggestions } from "@/lib/api/hooks/use-drive-queries"
import { openDriveItem } from "@/lib/drive/drive-open-item"
import {
type DriveSearchScope,
buildDriveSearchUrl,
defaultDriveSearchScope,
fileBrowserViewForSearchScope,
} from "@/lib/drive/drive-search"
import { useDriveRouteRoot } from "@/lib/drive/drive-route-context"
import type { DriveView } from "@/lib/drive/drive-url"
import { useDebouncedValue } from "@/lib/hooks/use-debounced-value"
import { useDriveUIStore } from "@/lib/stores/drive-ui-store"
import { DRIVE_SEARCH_INPUT_WRAP_CLASS } from "@/lib/drive/drive-chrome-classes"
import { cn } from "@/lib/utils"
interface DriveSearchBarProps {
value: string
onChange: (value: string) => void
scope: DriveSearchScope
onScopeChange: (scope: DriveSearchScope) => void
folderPath: string
contextView: DriveView
/** When true, suggestions panel hidden (search results page). */
resultsMode?: boolean
className?: string
autoFocus?: boolean
}
export function DriveSearchBar({
value,
onChange,
scope,
onScopeChange,
folderPath,
contextView,
resultsMode = false,
className,
autoFocus = false,
}: DriveSearchBarProps) {
const router = useRouter()
const routeRoot = useDriveRouteRoot()
const openPreview = useDriveUIStore((s) => s.openPreview)
const inputRef = useRef<HTMLInputElement>(null)
const [focused, setFocused] = useState(false)
const debouncedQuery = useDebouncedValue(value, 250)
const showFolderScope = folderPath !== "/"
const effectiveScope =
scope === "folder" && !showFolderScope
? defaultDriveSearchScope(contextView, folderPath)
: scope
const searchPath = effectiveScope === "folder" ? folderPath : "/"
const { data, isFetching } = useDriveSearchSuggestions(
debouncedQuery,
effectiveScope,
searchPath,
focused && !resultsMode && debouncedQuery.trim().length >= 2
)
const suggestions = data?.files ?? []
const showPanel =
!resultsMode && focused && value.trim().length >= 2
const submitSearch = (query?: string) => {
const q = (query ?? value).trim()
if (!q) return
router.push(
buildDriveSearchUrl(
{
query: q,
scope: effectiveScope,
folderPath: effectiveScope === "folder" ? folderPath : "/",
},
routeRoot
)
)
inputRef.current?.blur()
}
const openSuggestion = (item: DriveFileInfo) => {
openDriveItem(item, {
router,
openPreview,
view: fileBrowserViewForSearchScope(effectiveScope),
contextItems: suggestions,
})
inputRef.current?.blur()
}
return (
<div
data-drive-search
className={cn(
"relative flex w-full min-w-0 flex-col overflow-visible",
className
)}
>
<div className={cn(DRIVE_SEARCH_INPUT_WRAP_CLASS, "text-muted-foreground")}>
<div className="pointer-events-none absolute left-3.5 flex items-center">
<Search className="size-5 shrink-0" />
</div>
<input
ref={inputRef}
type="text"
enterKeyHint="search"
placeholder="Rechercher dans Drive"
value={value}
onChange={(e) => onChange(e.target.value)}
onFocus={() => setFocused(true)}
onBlur={() => window.setTimeout(() => setFocused(false), 150)}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault()
submitSearch()
}
if (e.key === "Escape") {
onChange("")
inputRef.current?.blur()
}
}}
autoFocus={autoFocus}
className={cn(
"h-full w-full rounded-full border-0 bg-transparent text-sm text-foreground outline-none placeholder:text-muted-foreground",
value ? "pl-11 pr-12" : "pl-11 pr-4"
)}
autoComplete="off"
/>
{value && (
<Button
variant="ghost"
size="icon"
className="absolute right-2 text-gray-600"
onMouseDown={(e) => e.preventDefault()}
onClick={() => {
onChange("")
inputRef.current?.focus()
}}
aria-label="Effacer la recherche"
>
<X className="h-5 w-5" />
</Button>
)}
</div>
{showPanel ? (
<DriveSearchSuggestionsPanel
query={value}
scope={effectiveScope}
onScopeChange={onScopeChange}
showFolderScope={showFolderScope}
suggestions={suggestions}
loading={isFetching && suggestions.length === 0}
onPickItem={openSuggestion}
onSubmitSearch={() => submitSearch()}
/>
) : null}
</div>
)
}