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

258 lines
8.1 KiB
TypeScript

"use client"
import type { Editor } from "@tiptap/react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import {
buildGradientCss,
DOCS_GRAPHIC_PLACEMENT_LABELS,
DOCS_GRAPHIC_WRAP_LABELS,
parseGraphicAttrs,
type DocsGraphicPlacement,
type DocsGraphicWrap,
} from "@/lib/drive/docs-graphic-types"
import { readGraphicToolbarActive } from "@/components/drive/richtext/docs-graphic-toolbar-menu"
export function DocsGraphicOptionsPanel({
editor,
disabled,
open,
onOpenChange,
}: {
editor: Editor | null
disabled?: boolean
open?: boolean
onOpenChange?: (open: boolean) => void
}) {
if (!editor || !readGraphicToolbarActive(editor)) return null
const name = editor.isActive("docsInlineGraphic") ? "docsInlineGraphic" : "docsGraphic"
const attrs = parseGraphicAttrs(editor.getAttributes(name) as Record<string, unknown>)
const update = (patch: Record<string, unknown>) => {
editor.chain().focus().updateDocsGraphic(patch).run()
}
const numField = (
label: string,
key: "width" | "height" | "x" | "y" | "rotationDeg",
step = 1
) => (
<div className="grid gap-1">
<Label className="text-xs text-muted-foreground">{label}</Label>
<Input
type="number"
className="h-8"
disabled={disabled}
value={attrs[key]}
step={step}
onChange={(e) => update({ [key]: Number(e.target.value) || 0 })}
/>
</div>
)
return (
<Popover open={open} onOpenChange={onOpenChange}>
<PopoverTrigger asChild>
<Button
type="button"
variant="ghost"
size="sm"
className="docs-toolbar-btn h-7 shrink-0 px-2 text-xs"
disabled={disabled}
>
Options
</Button>
</PopoverTrigger>
<PopoverContent className="w-72 space-y-3 p-3" align="start">
<p className="text-sm font-medium">
{attrs.graphicType === "image"
? "Options image"
: attrs.graphicType === "shape"
? "Options forme"
: "Options dégradé"}
</p>
<div className="grid grid-cols-2 gap-2">
{numField("Largeur (px)", "width")}
{numField("Hauteur (px)", "height")}
{numField("X", "x")}
{numField("Y", "y")}
{numField("Rotation (°)", "rotationDeg")}
</div>
<div className="grid gap-1">
<Label className="text-xs text-muted-foreground">Habillage</Label>
<Select
disabled={disabled}
value={attrs.wrap}
onValueChange={(v) => update({ wrap: v as DocsGraphicWrap })}
>
<SelectTrigger className="h-8">
<SelectValue />
</SelectTrigger>
<SelectContent>
{(Object.keys(DOCS_GRAPHIC_WRAP_LABELS) as DocsGraphicWrap[]).map((wrap) => (
<SelectItem key={wrap} value={wrap}>
{DOCS_GRAPHIC_WRAP_LABELS[wrap]}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="grid gap-1">
<Label className="text-xs text-muted-foreground">Placement</Label>
<Select
disabled={disabled}
value={attrs.placement}
onValueChange={(v) => update({ placement: v as DocsGraphicPlacement })}
>
<SelectTrigger className="h-8">
<SelectValue />
</SelectTrigger>
<SelectContent>
{(Object.keys(DOCS_GRAPHIC_PLACEMENT_LABELS) as DocsGraphicPlacement[]).map(
(placement) => (
<SelectItem key={placement} value={placement}>
{DOCS_GRAPHIC_PLACEMENT_LABELS[placement]}
</SelectItem>
)
)}
</SelectContent>
</Select>
</div>
{attrs.graphicType === "shape" ? (
<div className="grid grid-cols-2 gap-2">
<div className="grid gap-1">
<Label className="text-xs text-muted-foreground">Remplissage</Label>
<Input
type="color"
className="h-8 p-1"
disabled={disabled}
value={attrs.fill}
onChange={(e) => update({ fill: e.target.value })}
/>
</div>
<div className="grid gap-1">
<Label className="text-xs text-muted-foreground">Contour</Label>
<Input
type="color"
className="h-8 p-1"
disabled={disabled}
value={attrs.stroke}
onChange={(e) => update({ stroke: e.target.value })}
/>
</div>
<div className="col-span-2 grid gap-1">
<Label className="text-xs text-muted-foreground">Épaisseur contour</Label>
<Input
type="number"
className="h-8"
disabled={disabled}
min={0}
value={attrs.strokeWidth}
onChange={(e) => update({ strokeWidth: Number(e.target.value) || 0 })}
/>
</div>
</div>
) : null}
{attrs.graphicType === "gradient" ? (
<div className="grid grid-cols-2 gap-2">
<div className="grid gap-1">
<Label className="text-xs text-muted-foreground">Couleur 1</Label>
<Input
type="color"
className="h-8 p-1"
disabled={disabled}
value={attrs.gradientColor1}
onChange={(e) => {
const gradientColor1 = e.target.value
update({
gradientColor1,
gradientCss: buildGradientCss(
attrs.gradientAngle,
gradientColor1,
attrs.gradientColor2
),
})
}}
/>
</div>
<div className="grid gap-1">
<Label className="text-xs text-muted-foreground">Couleur 2</Label>
<Input
type="color"
className="h-8 p-1"
disabled={disabled}
value={attrs.gradientColor2}
onChange={(e) => {
const gradientColor2 = e.target.value
update({
gradientColor2,
gradientCss: buildGradientCss(
attrs.gradientAngle,
attrs.gradientColor1,
gradientColor2
),
})
}}
/>
</div>
<div className="col-span-2 grid gap-1">
<Label className="text-xs text-muted-foreground">Angle (°)</Label>
<Input
type="number"
className="h-8"
disabled={disabled}
value={attrs.gradientAngle}
onChange={(e) => {
const gradientAngle = Number(e.target.value) || 0
update({
gradientAngle,
gradientCss: buildGradientCss(
gradientAngle,
attrs.gradientColor1,
attrs.gradientColor2
),
})
}}
/>
</div>
</div>
) : null}
{attrs.graphicType === "image" ? (
<Button
type="button"
variant="outline"
size="sm"
className="w-full"
disabled={disabled}
onClick={() =>
update({ cropX: 0, cropY: 0, cropWidth: 1, cropHeight: 1 })
}
>
Réinitialiser le recadrage
</Button>
) : null}
</PopoverContent>
</Popover>
)
}