ultisuite-client/components/drive/richtext/docs-paragraph-style-ui.tsx
R3D347HR4Y 770669424e
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
feat(drive): refine select trigger styles in rich text editor
- Updated select trigger components for paragraph style and font family to include a ghost variant for improved visual consistency.
- Adjusted CSS styles to standardize height and padding, enhancing the overall appearance of select elements.
- Improved hover and focus states for better user interaction feedback across different themes.
2026-06-15 18:03:02 +02:00

181 lines
5.8 KiB
TypeScript

"use client"
import { useMemo, useState } from "react"
import {
buildParagraphStyleMenuEntries,
type DocParagraphStyleDefinition,
type DocParagraphStylesCatalog,
} from "@/lib/drive/docs-paragraph-styles"
import { paragraphStylePreviewStyle } from "@/lib/drive/docs-paragraph-styles-css"
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectSeparator,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { cn } from "@/lib/utils"
export function DocsParagraphStyleSelect({
value,
disabled,
documentStyles,
userStyles,
onValueChange,
triggerClassName,
}: {
value: string
disabled?: boolean
documentStyles: DocParagraphStylesCatalog
userStyles: DocParagraphStylesCatalog
onValueChange: (styleId: string) => void
triggerClassName?: string
}) {
const entries = useMemo(
() => buildParagraphStyleMenuEntries(documentStyles, userStyles),
[documentStyles, userStyles]
)
const documentEntries = entries.filter((entry) => entry.section === "document")
const userEntries = entries.filter((entry) => entry.section === "user")
const active = entries.find((entry) => entry.definition.id === value)?.definition
return (
<Select disabled={disabled} value={value} onValueChange={onValueChange}>
<SelectTrigger
variant="ghost"
className={cn(
"docs-toolbar-select docs-toolbar-select--style h-7 w-[120px] shrink-0 border-0 bg-transparent px-1 py-0 shadow-none",
triggerClassName
)}
>
<SelectValue placeholder="Style">{active?.name ?? "Texte normal"}</SelectValue>
</SelectTrigger>
<SelectContent className="docs-toolbar-select-content docs-toolbar-select-content--style max-h-[min(70vh,480px)]">
<SelectGroup>
<SelectLabel>Styles du document</SelectLabel>
{documentEntries.map(({ definition }) => (
<SelectItem
key={definition.id}
value={definition.id}
className="docs-toolbar-style-item"
>
<span className="docs-toolbar-style-preview" style={paragraphStylePreviewStyle(definition)}>
{definition.name}
</span>
</SelectItem>
))}
</SelectGroup>
{userEntries.length > 0 ? (
<>
<SelectSeparator />
<SelectGroup>
<SelectLabel>Mes styles</SelectLabel>
{userEntries.map(({ definition }) => (
<SelectItem
key={`user-${definition.id}`}
value={definition.id}
className="docs-toolbar-style-item"
>
<span
className="docs-toolbar-style-preview"
style={paragraphStylePreviewStyle(definition)}
>
{definition.name}
</span>
</SelectItem>
))}
</SelectGroup>
</>
) : null}
</SelectContent>
</Select>
)
}
export function DocsCreateStyleDialog({
open,
onOpenChange,
onCreate,
}: {
open: boolean
onOpenChange: (open: boolean) => void
onCreate: (input: { name: string; basedOn: string }) => void
}) {
const [name, setName] = useState("")
const [basedOn, setBasedOn] = useState("normal")
return open ? (
<div className="fixed inset-0 z-[200] flex items-center justify-center bg-black/30 p-4">
<div
className="w-full max-w-md rounded-lg border bg-background p-4 shadow-lg"
role="dialog"
aria-modal="true"
aria-labelledby="docs-create-style-title"
>
<h2 id="docs-create-style-title" className="text-base font-medium">
Créer un style
</h2>
<p className="mt-1 text-sm text-muted-foreground">
Le nouveau style sera enregistré dans vos styles personnels.
</p>
<div className="mt-4 space-y-3">
<label className="block space-y-1">
<span className="text-sm">Nom</span>
<input
className="flex h-9 w-full rounded-md border bg-background px-3 text-sm"
value={name}
onChange={(event) => setName(event.target.value)}
placeholder="Mon style"
/>
</label>
<label className="block space-y-1">
<span className="text-sm">Basé sur</span>
<select
className="flex h-9 w-full rounded-md border bg-background px-3 text-sm"
value={basedOn}
onChange={(event) => setBasedOn(event.target.value)}
>
<option value="normal">Normal</option>
<option value="title">Titre</option>
<option value="subtitle">Sous-titre</option>
<option value="heading1">Titre 1</option>
<option value="heading2">Titre 2</option>
<option value="heading3">Titre 3</option>
<option value="heading4">Titre 4</option>
</select>
</label>
</div>
<div className="mt-4 flex justify-end gap-2">
<button
type="button"
className="rounded-md px-3 py-1.5 text-sm hover:bg-muted"
onClick={() => onOpenChange(false)}
>
Annuler
</button>
<button
type="button"
className="rounded-md bg-primary px-3 py-1.5 text-sm text-primary-foreground"
onClick={() => {
onCreate({ name: name.trim() || "Style personnalisé", basedOn })
setName("")
setBasedOn("normal")
onOpenChange(false)
}}
>
Créer
</button>
</div>
</div>
</div>
) : null
}
export function paragraphStyleSubmenuLabel(definition: DocParagraphStyleDefinition) {
return definition.name
}