ultisuite-client/components/drive/office-editor-inline-title.tsx
2026-06-09 17:06:20 +02:00

127 lines
3.2 KiB
TypeScript

"use client"
import { useEffect, useRef, useState } from "react"
import { cn } from "@/lib/utils"
const FIELD_BASE =
"col-start-1 row-start-1 w-full min-w-0 rounded-md border border-transparent bg-transparent px-2 py-0.5 text-left text-sm font-medium outline-none transition-[background-color,border-color]"
export function OfficeEditorInlineTitle({
value,
onRename,
disabled = false,
className,
}: {
value: string
onRename: (next: string) => Promise<void>
disabled?: boolean
className?: string
}) {
const [editing, setEditing] = useState(false)
const [draft, setDraft] = useState(value)
const [busy, setBusy] = useState(false)
const inputRef = useRef<HTMLInputElement>(null)
const skipBlurCommitRef = useRef(false)
useEffect(() => {
if (!editing) setDraft(value)
}, [value, editing])
useEffect(() => {
if (!editing) return
const timer = window.setTimeout(() => {
const el = inputRef.current
if (!el) return
el.focus()
el.select()
}, 0)
return () => window.clearTimeout(timer)
}, [editing])
const cancelEdit = () => {
skipBlurCommitRef.current = true
setDraft(value)
setEditing(false)
}
const commitEdit = async () => {
if (skipBlurCommitRef.current) {
skipBlurCommitRef.current = false
return
}
const trimmed = draft.trim()
if (!trimmed || trimmed === value) {
setDraft(value)
setEditing(false)
return
}
setBusy(true)
try {
await onRename(trimmed)
setEditing(false)
} catch {
setDraft(value)
setEditing(false)
} finally {
setBusy(false)
}
}
const mirrorText = (editing ? draft : value) || " "
const fieldClass = cn(FIELD_BASE, className)
return (
<div className="inline-grid w-fit max-w-[min(480px,45vw)]">
<span
className={cn(fieldClass, "invisible whitespace-pre")}
aria-hidden
>
{mirrorText}
</span>
{editing && !disabled ? (
<input
ref={inputRef}
value={draft}
disabled={busy}
aria-label="Nom du fichier"
className={cn(
fieldClass,
"text-foreground focus-visible:border-border focus-visible:ring-0"
)}
onChange={(event) => setDraft(event.target.value)}
onKeyDown={(event) => {
if (event.key === "Enter") {
event.preventDefault()
void commitEdit()
}
if (event.key === "Escape") {
event.preventDefault()
cancelEdit()
}
}}
onBlur={() => void commitEdit()}
/>
) : (
<button
type="button"
disabled={disabled || busy}
title={disabled ? undefined : "Renommer"}
className={cn(
fieldClass,
"truncate text-foreground",
disabled
? "cursor-default"
: "cursor-text hover:bg-muted/70 focus-visible:border-border focus-visible:ring-0"
)}
onClick={() => {
if (!disabled && !busy) setEditing(true)
}}
>
{value}
</button>
)}
</div>
)
}