127 lines
3.2 KiB
TypeScript
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>
|
|
)
|
|
}
|