172 lines
5.7 KiB
TypeScript
172 lines
5.7 KiB
TypeScript
"use client"
|
|
|
|
import { useEffect, useState } from "react"
|
|
import { Button } from "@/components/ui/button"
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog"
|
|
import { Input } from "@/components/ui/input"
|
|
import { Label } from "@/components/ui/label"
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select"
|
|
import {
|
|
DOCS_DEFAULT_LINE_HEIGHT,
|
|
DOCS_LINE_HEIGHT_PRESETS,
|
|
} from "@/lib/drive/docs-line-spacing"
|
|
import type { DocsParagraphSpacingAttrs } from "@/lib/drive/docs-line-spacing"
|
|
import {
|
|
DRIVE_BTN_GHOST,
|
|
DRIVE_BTN_PRIMARY,
|
|
DRIVE_DIALOG_BODY,
|
|
DRIVE_DIALOG_CONTENT,
|
|
DRIVE_DIALOG_FOOTER,
|
|
DRIVE_DIALOG_HEADER,
|
|
DRIVE_FIELD_CLASS,
|
|
DRIVE_LABEL_CLASS,
|
|
DRIVE_TEXT_TITLE,
|
|
} from "@/lib/drive/drive-dialog-styles"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
export function DocsLineSpacingDialog({
|
|
open,
|
|
onOpenChange,
|
|
initial,
|
|
onApply,
|
|
}: {
|
|
open: boolean
|
|
onOpenChange: (open: boolean) => void
|
|
initial: DocsParagraphSpacingAttrs
|
|
onApply: (input: Pick<DocsParagraphSpacingAttrs, "lineHeight" | "spaceBeforePt" | "spaceAfterPt">) => void
|
|
}) {
|
|
const [lineHeightPreset, setLineHeightPreset] = useState<string>("1.15")
|
|
const [customLineHeight, setCustomLineHeight] = useState(String(DOCS_DEFAULT_LINE_HEIGHT))
|
|
const [spaceBefore, setSpaceBefore] = useState("0")
|
|
const [spaceAfter, setSpaceAfter] = useState("0")
|
|
|
|
useEffect(() => {
|
|
if (!open) return
|
|
const height = initial.lineHeight ?? DOCS_DEFAULT_LINE_HEIGHT
|
|
const preset = DOCS_LINE_HEIGHT_PRESETS.find((item) => Math.abs(item.value - height) < 0.001)
|
|
setLineHeightPreset(preset?.id ?? "custom")
|
|
setCustomLineHeight(String(height))
|
|
setSpaceBefore(String(initial.spaceBeforePt))
|
|
setSpaceAfter(String(initial.spaceAfterPt))
|
|
}, [initial, open])
|
|
|
|
const resolvedLineHeight = (): number | null => {
|
|
if (lineHeightPreset === "custom") {
|
|
const value = Number.parseFloat(customLineHeight)
|
|
return Number.isFinite(value) && value > 0 ? value : DOCS_DEFAULT_LINE_HEIGHT
|
|
}
|
|
const preset = DOCS_LINE_HEIGHT_PRESETS.find((item) => item.id === lineHeightPreset)
|
|
return preset?.value ?? DOCS_DEFAULT_LINE_HEIGHT
|
|
}
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent className={cn(DRIVE_DIALOG_CONTENT, "max-w-md gap-0 p-0")}>
|
|
<DialogHeader className={DRIVE_DIALOG_HEADER}>
|
|
<DialogTitle className={cn("text-base font-medium", DRIVE_TEXT_TITLE)}>
|
|
Espacement personnalisé
|
|
</DialogTitle>
|
|
</DialogHeader>
|
|
|
|
<div className={cn(DRIVE_DIALOG_BODY, "space-y-4")}>
|
|
<div className="space-y-2">
|
|
<Label className={DRIVE_LABEL_CLASS} htmlFor="docs-line-height-preset">
|
|
Interligne
|
|
</Label>
|
|
<Select value={lineHeightPreset} onValueChange={setLineHeightPreset}>
|
|
<SelectTrigger id="docs-line-height-preset" className={DRIVE_FIELD_CLASS}>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{DOCS_LINE_HEIGHT_PRESETS.map((preset) => (
|
|
<SelectItem key={preset.id} value={preset.id}>
|
|
{preset.label}
|
|
</SelectItem>
|
|
))}
|
|
<SelectItem value="custom">Personnalisé</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
{lineHeightPreset === "custom" ? (
|
|
<Input
|
|
type="number"
|
|
min={0.5}
|
|
max={5}
|
|
step={0.05}
|
|
value={customLineHeight}
|
|
onChange={(event) => setCustomLineHeight(event.target.value)}
|
|
className={DRIVE_FIELD_CLASS}
|
|
aria-label="Interligne personnalisé"
|
|
/>
|
|
) : null}
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="space-y-2">
|
|
<Label className={DRIVE_LABEL_CLASS} htmlFor="docs-space-before">
|
|
Avant (pt)
|
|
</Label>
|
|
<Input
|
|
id="docs-space-before"
|
|
type="number"
|
|
min={0}
|
|
max={108}
|
|
step={1}
|
|
value={spaceBefore}
|
|
onChange={(event) => setSpaceBefore(event.target.value)}
|
|
className={DRIVE_FIELD_CLASS}
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label className={DRIVE_LABEL_CLASS} htmlFor="docs-space-after">
|
|
Après (pt)
|
|
</Label>
|
|
<Input
|
|
id="docs-space-after"
|
|
type="number"
|
|
min={0}
|
|
max={108}
|
|
step={1}
|
|
value={spaceAfter}
|
|
onChange={(event) => setSpaceAfter(event.target.value)}
|
|
className={DRIVE_FIELD_CLASS}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<DialogFooter className={DRIVE_DIALOG_FOOTER}>
|
|
<Button type="button" variant="ghost" className={DRIVE_BTN_GHOST} onClick={() => onOpenChange(false)}>
|
|
Annuler
|
|
</Button>
|
|
<Button
|
|
type="button"
|
|
className={DRIVE_BTN_PRIMARY}
|
|
onClick={() => {
|
|
onApply({
|
|
lineHeight: resolvedLineHeight(),
|
|
spaceBeforePt: Math.max(0, Number.parseFloat(spaceBefore) || 0),
|
|
spaceAfterPt: Math.max(0, Number.parseFloat(spaceAfter) || 0),
|
|
})
|
|
onOpenChange(false)
|
|
}}
|
|
>
|
|
Appliquer
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|