ultisuite-client/components/drive/drive-search-suggestions.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

174 lines
5.3 KiB
TypeScript

"use client"
import { useRouter } from "next/navigation"
import { FolderOpen, Loader2 } from "lucide-react"
import type { DriveFileInfo } from "@/lib/api/types"
import { DriveFileTypeIcon } from "@/lib/drive/drive-file-icon"
import { Button } from "@/components/ui/button"
import {
type DriveSearchScope,
DRIVE_SEARCH_SCOPES,
driveSearchScopeShortLabel,
itemLocationLabel,
itemParentFolderPath,
} from "@/lib/drive/drive-search"
import { driveFolderHref } from "@/lib/drive/drive-sidebar-tree"
import { displayFileName } from "@/lib/drive/display-file-name"
import { useDriveRouteRoot } from "@/lib/drive/drive-route-context"
import { MAIL_SEARCH_SUGGESTIONS_DROPDOWN_CLASS } from "@/lib/mail-chrome-classes"
import { cn } from "@/lib/utils"
function ScopePicker({
scope,
onScopeChange,
showFolderScope,
}: {
scope: DriveSearchScope
onScopeChange: (scope: DriveSearchScope) => void
showFolderScope: boolean
}) {
const options = DRIVE_SEARCH_SCOPES.filter((s) => s !== "folder" || showFolderScope)
return (
<div className="flex flex-wrap gap-1.5 px-3 pb-2 pt-1">
{options.map((option) => (
<button
key={option}
type="button"
onMouseDown={(e) => e.preventDefault()}
onClick={() => onScopeChange(option)}
className={cn(
"rounded-full px-3 py-1 text-xs font-medium transition-colors",
scope === option
? "bg-mail-active text-[#1967d2] dark:text-[#8ab4f8]"
: "bg-mail-surface-muted text-mail-text-muted hover:bg-mail-nav-hover"
)}
>
{driveSearchScopeShortLabel(option)}
</button>
))}
</div>
)
}
function SuggestionRow({
item,
scope,
onPick,
}: {
item: DriveFileInfo
scope: DriveSearchScope
onPick: (item: DriveFileInfo) => void
}) {
const router = useRouter()
const routeRoot = useDriveRouteRoot()
const view = scope === "shared" ? "shared" : "files"
const parentPath = itemParentFolderPath(item.path, item.type)
const parentHref = driveFolderHref(view, parentPath, undefined, routeRoot)
const location = itemLocationLabel(item.path, item.type)
return (
<div className="group flex min-w-0 items-center gap-2 rounded-lg px-2 py-1.5 hover:bg-mail-nav-hover">
<button
type="button"
className="flex min-w-0 flex-1 items-center gap-3 text-left"
onMouseDown={(e) => e.preventDefault()}
onClick={() => onPick(item)}
>
<DriveFileTypeIcon file={item} className="size-5 shrink-0" />
<span className="min-w-0 flex-1">
<span className="block truncate text-sm text-[#202124] dark:text-foreground">
{displayFileName(item.name)}
</span>
<span className="block truncate text-xs text-[#5f6368] dark:text-muted-foreground">
{location}
</span>
</span>
</button>
<Button
type="button"
variant="ghost"
size="icon"
className="size-8 shrink-0 text-[#5f6368] opacity-0 transition-opacity group-hover:opacity-100 focus-visible:opacity-100 dark:text-muted-foreground"
aria-label="Ouvrir le dossier parent"
onMouseDown={(e) => e.preventDefault()}
onClick={() => router.push(parentHref)}
>
<FolderOpen className="size-4" />
</Button>
</div>
)
}
export function DriveSearchSuggestionsPanel({
query,
scope,
onScopeChange,
showFolderScope,
suggestions,
loading,
onPickItem,
onSubmitSearch,
className,
}: {
query: string
scope: DriveSearchScope
onScopeChange: (scope: DriveSearchScope) => void
showFolderScope: boolean
suggestions: DriveFileInfo[]
loading: boolean
onPickItem: (item: DriveFileInfo) => void
onSubmitSearch: () => void
className?: string
}) {
const trimmed = query.trim()
if (trimmed.length < 2) return null
return (
<div
data-drive-search-suggestions
className={cn(
MAIL_SEARCH_SUGGESTIONS_DROPDOWN_CLASS,
"top-[calc(100%+6px)] rounded-2xl",
className
)}
>
<ScopePicker
scope={scope}
onScopeChange={onScopeChange}
showFolderScope={showFolderScope}
/>
<div className="border-t border-mail-border-subtle px-1 py-1">
{loading ? (
<div className="flex items-center gap-2 px-3 py-4 text-sm text-[#5f6368] dark:text-muted-foreground">
<Loader2 className="size-4 animate-spin" />
Recherche
</div>
) : suggestions.length === 0 ? (
<p className="px-3 py-4 text-sm text-[#5f6368] dark:text-muted-foreground">
Aucune suggestion pour « {trimmed} »
</p>
) : (
<>
{suggestions.map((item) => (
<SuggestionRow
key={item.path}
item={item}
scope={scope}
onPick={onPickItem}
/>
))}
<button
type="button"
className="mt-0.5 w-full rounded-lg px-3 py-2 text-left text-sm font-medium text-[#1967d2] hover:bg-mail-nav-hover dark:text-[#8ab4f8]"
onMouseDown={(e) => e.preventDefault()}
onClick={onSubmitSearch}
>
Tous les résultats pour « {trimmed} »
</button>
</>
)}
</div>
</div>
)
}