ultisuite-client/components/drive/richtext/docs-chrome.tsx
R3D347HR4Y 2a7c153748
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
wrap page
2026-06-10 12:48:27 +02:00

206 lines
7.1 KiB
TypeScript

"use client"
import type { ReactNode } from "react"
import Link from "next/link"
import { Globe, Lock, Star, Users } from "lucide-react"
import { EditorAccountButton } from "@/components/drive/editor-account-button"
import { OfficeEditorInlineTitle } from "@/components/drive/office-editor-inline-title"
import { ShareDialog } from "@/components/drive/share-dialog"
import { CollabPresenceAvatars } from "@/components/drive/richtext/collab-presence-avatars"
import { DocsLogoIcon } from "@/components/drive/richtext/docs-logo-icon"
import { DocsMenubar } from "@/components/drive/richtext/docs-menubar"
import type { DocsEditMenuActions, DocsEditMenuState } from "@/components/drive/richtext/docs-edit-menu"
import type { DocsFileMenuActions } from "@/components/drive/richtext/docs-file-menu"
import type { DocsViewMenuActions, DocsViewMenuState } from "@/components/drive/richtext/docs-view-menu"
import { DocsMoveButton } from "@/components/drive/richtext/docs-move-button"
import { Button } from "@/components/ui/button"
import type { DriveShare, DriveFileInfo } from "@/lib/api/types"
import type { CollabPresenceUser } from "@/lib/drive/use-collab-presence"
import {
resolveShareButtonIcon,
type ShareButtonIcon,
} from "@/lib/drive/drive-share-button-state"
import type { RichTextSaveStatus } from "@/lib/drive/richtext-types"
import { cn } from "@/lib/utils"
function ShareButtonIcon({ kind }: { kind: ShareButtonIcon }) {
if (kind === "globe") return <Globe className="h-4 w-4" aria-hidden />
if (kind === "users") return <Users className="h-4 w-4" aria-hidden />
return <Lock className="h-4 w-4" aria-hidden />
}
function saveStatusLabel(status: RichTextSaveStatus): string {
switch (status) {
case "saving":
return "Enregistrement…"
case "saved":
return "Enregistré dans UltiDrive"
case "error":
return "Erreur d'enregistrement"
default:
return ""
}
}
export function DocsChrome({
title,
onRename,
renameDisabled,
backHref,
backLabel,
showBack = true,
shares = [],
onShareClick,
showShare = false,
showAccount = false,
saveStatus = "idle",
presenceUsers = [],
viewMenuActions,
viewMenuState,
viewMenuDisabled,
trailing,
moveFile,
onFileMoved,
fileMenuActions,
fileMenuDialogs,
fileMenuDisabled,
editMenuActions,
editMenuState,
editMenuDisabled,
renameSignal,
}: {
title: string
onRename?: (next: string) => Promise<void>
renameDisabled?: boolean
backHref?: string
backLabel?: string
showBack?: boolean
shares?: DriveShare[]
onShareClick?: () => void
showShare?: boolean
showAccount?: boolean
saveStatus?: RichTextSaveStatus
presenceUsers?: CollabPresenceUser[]
viewMenuActions?: DocsViewMenuActions
viewMenuState?: DocsViewMenuState
viewMenuDisabled?: boolean
trailing?: ReactNode
/** Propriétaire uniquement — affiche le bouton déplacer. */
moveFile?: DriveFileInfo
onFileMoved?: (newPath: string) => void
fileMenuActions?: DocsFileMenuActions
fileMenuDialogs?: ReactNode
fileMenuDisabled?: boolean
editMenuActions?: DocsEditMenuActions
editMenuState?: DocsEditMenuState
editMenuDisabled?: boolean
renameSignal?: number
}) {
const shareIcon = resolveShareButtonIcon(shares)
const statusText = saveStatusLabel(saveStatus)
const iconHref = showBack !== false && backHref ? backHref : "/drive"
const iconLabel =
showBack !== false && backHref
? (backLabel ?? "Retour au Drive")
: "Ouvrir le Drive"
return (
<>
<header className="shrink-0 bg-white dark:bg-background">
<div className="flex min-h-[72px] items-center gap-0 px-2 py-1">
<Button
variant="ghost"
size="icon"
className="mr-1 size-11 shrink-0 self-center rounded-full hover:bg-[#e8eaed] dark:hover:bg-muted"
asChild
>
<Link href={iconHref} aria-label={iconLabel}>
<DocsLogoIcon className="size-[38px]" />
</Link>
</Button>
<div className="min-w-0 flex-1 self-center">
<div className="docs-chrome-title-row flex min-w-0 items-center gap-0 leading-tight">
{onRename ? (
<OfficeEditorInlineTitle
value={title}
onRename={onRename}
disabled={renameDisabled}
renameSignal={renameSignal}
className="pr-1 text-base font-normal"
/>
) : (
<span className="block truncate pl-2 pr-1 text-base font-normal">{title}</span>
)}
<div className="flex shrink-0 items-center -space-x-0.5">
<Button
type="button"
variant="ghost"
size="icon"
className="size-7 shrink-0 text-muted-foreground"
disabled
aria-label="Ajouter aux favoris (bientôt)"
>
<Star className="size-4" />
</Button>
{moveFile && onFileMoved ? (
<DocsMoveButton file={moveFile} onFileMoved={onFileMoved} />
) : null}
</div>
{statusText ? (
<span
className={cn(
"ml-1.5 hidden min-w-0 line-clamp-1 overflow-hidden break-all text-ellipsis text-xs text-[#5f6368] sm:inline dark:text-muted-foreground",
saveStatus === "error" && "text-destructive"
)}
>
{statusText}
</span>
) : null}
</div>
<div className="-mt-1 flex min-w-0 items-center overflow-x-auto overflow-y-visible">
<DocsMenubar
className="docs-menubar shrink-0"
viewMenuActions={viewMenuActions}
viewMenuState={viewMenuState}
viewMenuDisabled={viewMenuDisabled}
fileMenuActions={fileMenuActions}
fileMenuDisabled={fileMenuDisabled}
editMenuActions={editMenuActions}
editMenuState={editMenuState}
editMenuDisabled={editMenuDisabled}
/>
</div>
</div>
<div className="flex shrink-0 items-center gap-2 self-center pl-2">
{trailing}
{presenceUsers.length > 0 ? (
<CollabPresenceAvatars users={presenceUsers} />
) : null}
{showShare ? (
<Button
type="button"
size="sm"
className={cn(
"gap-2 rounded-full border-0 px-5 shadow-none",
"bg-[#1967d2] text-white hover:bg-[#185abc] hover:text-white",
"dark:bg-[#e8eaed] dark:text-[#3c4043] dark:hover:bg-[#dadce0] dark:hover:text-[#202124]"
)}
onClick={onShareClick}
>
<ShareButtonIcon kind={shareIcon} />
Partager
</Button>
) : null}
{showAccount ? <EditorAccountButton /> : null}
</div>
</div>
</header>
{showShare ? <ShareDialog /> : null}
{fileMenuDialogs}
</>
)
}