"use client"
import { useMemo, useState, type ReactNode } from "react"
import { Icon } from "@iconify/react"
import {
AlignCenter,
AlignJustify,
AlignLeft,
AlignRight,
Bold,
Check,
Italic,
List,
ListOrdered,
Strikethrough,
Underline,
} from "lucide-react"
import {
MenubarContent,
MenubarItem,
MenubarMenu,
MenubarSeparator,
MenubarShortcut,
MenubarSubContent,
MenubarSubTrigger,
MenubarTrigger,
} from "@/components/ui/menubar"
import {
DocsExclusiveMenuSub,
DocsExclusiveMenuSubRoot,
} from "@/components/drive/richtext/docs-exclusive-menu-sub"
import {
DocsFormatBulletPresets,
DocsFormatChecklistPresets,
DocsFormatColumnPresets,
DocsFormatNumberedPresets,
} from "@/components/drive/richtext/docs-format-menu-presets"
import { DocsCreateStyleDialog } from "@/components/drive/richtext/docs-paragraph-style-ui"
import { DocsLineSpacingDialog } from "@/components/drive/richtext/docs-line-spacing-dialog"
import { DocsListStartDialog } from "@/components/drive/richtext/docs-list-start-dialog"
import { DOCS_MENUBAR_CONTENT_PROPS } from "@/components/drive/richtext/docs-menubar-props"
import { DOCS_LINE_HEIGHT_PRESETS } from "@/lib/drive/docs-line-spacing"
import { buildParagraphStyleMenuEntries } from "@/lib/drive/docs-paragraph-styles"
import { useDocsParagraphStylesContext } from "@/lib/drive/docs-paragraph-styles-context"
import { docsModKeyLabel, isMacPlatform } from "@/lib/drive/docs-keyboard-shortcut"
import { DOCS_TABLE_BORDER_COLOR_PRESETS } from "@/lib/drive/docs-table-types"
import { cn } from "@/lib/utils"
export type DocsFormatMenuState = {
isBold: boolean
isItalic: boolean
isUnderline: boolean
isStrike: boolean
alignLeft: boolean
alignCenter: boolean
alignRight: boolean
alignJustify: boolean
isBulletList: boolean
isOrderedList: boolean
styleId: string
tableSelected: boolean
tableCanMerge: boolean
tableCanSplit: boolean
graphicSelected: boolean
imageSelected: boolean
lineHeightPresetId: string | "mixed" | null
hasSpaceBefore: boolean | "mixed"
hasSpaceAfter: boolean | "mixed"
keepWithNext: boolean | "mixed"
keepLinesTogether: boolean | "mixed"
preventWidowOrphan: boolean | "mixed"
pageBreakBefore: boolean | "mixed"
customSpacingDraft: import("@/lib/drive/docs-line-spacing").DocsParagraphSpacingAttrs
isTaskList: boolean
bulletStyleId: import("@/lib/drive/docs-list-styles").DocsBulletStyleId | null | "mixed"
orderedStyleId: import("@/lib/drive/docs-list-styles").DocsOrderedStyleId | null | "mixed"
checklistStyleId: import("@/lib/drive/docs-list-styles").DocsChecklistStyleId | null | "mixed"
orderedListStart: number | "mixed"
}
export type DocsFormatMenuActions = {
onBold: () => void
onItalic: () => void
onUnderline: () => void
onStrike: () => void
onAlignLeft: () => void
onAlignCenter: () => void
onAlignRight: () => void
onAlignJustify: () => void
onIncreaseIndent: () => void
onDecreaseIndent: () => void
onToggleBulletList: () => void
onToggleOrderedList: () => void
onApplyStyle: (styleId: string) => void
onClearFormatting: () => void
onPageSetup?: () => void
onTableAddRowBefore: () => void
onTableAddRowAfter: () => void
onTableAddColumnBefore: () => void
onTableAddColumnAfter: () => void
onTableDeleteRow: () => void
onTableDeleteColumn: () => void
onTableDelete: () => void
onTableMergeCells: () => void
onTableSplitCell: () => void
onTableToggleHeaderRow: () => void
onTableToggleHeaderColumn: () => void
onTableSetCellBackground: (color: string | null) => void
onTableAlignLeft: () => void
onTableAlignCenter: () => void
onTableAlignRight: () => void
onTableFixStructure: () => void
onTableSetCellBorders: (color: string) => void
onTableClearCellBorders: () => void
onTableSetRowHeight: (height: string | null) => void
onSetLineHeight: (lineHeight: number) => void
onToggleSpaceBefore: () => void
onToggleSpaceAfter: () => void
onApplyCustomSpacing: (input: {
lineHeight: number | null
spaceBeforePt: number
spaceAfterPt: number
}) => void
onToggleKeepWithNext: () => void
onToggleKeepLinesTogether: () => void
onTogglePreventWidowOrphan: () => void
onTogglePageBreakBefore: () => void
onApplyBulletStyle: (styleId: import("@/lib/drive/docs-list-styles").DocsBulletStyleId) => void
onApplyOrderedStyle: (styleId: import("@/lib/drive/docs-list-styles").DocsOrderedStyleId) => void
onApplyChecklistStyle: (styleId: import("@/lib/drive/docs-list-styles").DocsChecklistStyleId) => void
onRestartOrderedList: () => void
onContinueOrderedList: () => void
onSetOrderedListStart: (start: number) => void
}
const LINE_SPACING_OPTIONS = DOCS_LINE_HEIGHT_PRESETS
function MenuIcon({ children }: { children: ReactNode }) {
return {children}
}
function FormatShortcut({ children }: { children: ReactNode }) {
return {children}
}
function modShortcut(key: string, options?: { shift?: boolean; alt?: boolean }) {
const mod = docsModKeyLabel()
if (isMacPlatform()) {
const parts: string[] = []
if (options?.shift) parts.push("Maj")
if (options?.alt) parts.push("⌥")
if (parts.length > 0) return `${mod}+${parts.join("+")}+${key}`
return `${mod}${key}`
}
const parts: string[] = []
if (options?.shift) parts.push("Shift")
if (options?.alt) parts.push("Alt")
parts.push(mod, key)
return parts.join("+")
}
function CheckMenuOption({
checked,
onSelect,
children,
disabled,
className,
}: {
checked: boolean
onSelect: () => void
children: ReactNode
disabled?: boolean
className?: string
}) {
return (
{checked ? : null}
{children}
)
}
function ParagraphStyleSubmenu({
styleId,
label,
disabled,
onApply,
onUpdate,
menuDisabled,
}: {
styleId: string
label: string
disabled?: boolean
onApply: (styleId: string) => void
onUpdate: (styleId: string) => void
menuDisabled?: boolean
}) {
return (
{label}
onApply(styleId)}
>
Appliquer le style {label}
onUpdate(styleId)}
>
Mettre à jour "{label}" en fonction de la sélection/l'emplacement du
signe d'insertion
)
}
export function DocsFormatMenu({
actions,
state,
disabled,
}: {
actions: DocsFormatMenuActions
state: DocsFormatMenuState
disabled?: boolean
}) {
const paragraphStylesCtx = useDocsParagraphStylesContext()
const [createStyleOpen, setCreateStyleOpen] = useState(false)
const [customSpacingOpen, setCustomSpacingOpen] = useState(false)
const [listStartOpen, setListStartOpen] = useState(false)
const styleEntries = useMemo(() => {
if (!paragraphStylesCtx) return []
return buildParagraphStyleMenuEntries(
paragraphStylesCtx.state.documentStyles,
paragraphStylesCtx.state.userStyles
)
}, [paragraphStylesCtx])
const objectFormattingDisabled =
disabled || (!state.tableSelected && !state.graphicSelected && !state.imageSelected)
return (
Format
Texte
Gras
{modShortcut("B")}
Italique
{modShortcut("I")}
Souligner
{modShortcut("U")}
Barrer
{modShortcut("X", { shift: true })}
Sc
Petites majuscules
{isMacPlatform() ? "⌥+Maj+K" : "Alt+Shift+K"}
X2
Exposant
{modShortcut(".")}
X2
Indice
{modShortcut(",")}
Taille
Bientôt disponible
Minuscules/majuscules
Bientôt disponible
Styles de paragraphe
Bordures et ombrage
{styleEntries
.filter((entry) => entry.section === "document")
.map(({ definition }) => (
undefined)
}
/>
))}
{styleEntries.some((entry) => entry.section === "user") ? (
<>
Mes styles
{styleEntries
.filter((entry) => entry.section === "user")
.map(({ definition }) => (
undefined)
}
/>
))}
>
) : null}
Options
setCreateStyleOpen(true)}
>
Créer un style
Aligner et mettre en retrait
À gauche
{modShortcut("L", { shift: true })}
Centrer
{modShortcut("E", { shift: true })}
À droite
{modShortcut("R", { shift: true })}
Justifié
{modShortcut("J", { shift: true })}
Augmenter le retrait
{modShortcut("]")}
Diminuer le retrait
{modShortcut("[")}
Options de mise en retrait
Interligne et espace entre paragraphes
{LINE_SPACING_OPTIONS.map((option) => (
actions.onSetLineHeight(option.value)}
>
{option.label}
))}
Insérer un espacement avant le paragraphe
Insérer un espacement après le paragraphe
setCustomSpacingOpen(true)}
>
Espacement personnalisé
Rattacher au paragraphe suivant
Ne pas laisser de ligne isolée
Empêcher les lignes isolées
Ajouter un saut de page avant
Colonnes
Autres options
Puces et numéros
Options de liste
Recommencer la numérotation
Continuer la numérotation précédente
setListStartOpen(true)}
>
Modifier le numéro de départ…
Liste numérotée
Liste à puces
Menu de type checklist
En-têtes et pieds de page
Numéros de page
Orientation de la page
Passer au format Sans pages
Tableau
Insérer une ligne au-dessus
Insérer une ligne en dessous
Insérer une colonne à gauche
Insérer une colonne à droite
Fusionner les cellules
Scinder la cellule
Ligne d'en-tête
Colonne d'en-tête
Couleur de cellule
actions.onTableSetCellBackground(null)}
>
Aucune
{[
{ label: "Gris clair", color: "#f3f3f3" },
{ label: "Bleu clair", color: "#c9daf8" },
{ label: "Vert clair", color: "#d9ead3" },
{ label: "Jaune clair", color: "#fff2cc" },
].map((preset) => (
actions.onTableSetCellBackground(preset.color)}
>
{preset.label}
))}
Aligner le tableau à gauche
Centrer le tableau
Aligner le tableau à droite
Supprimer la ligne
Supprimer la colonne
Supprimer le tableau
Réparer la structure
Image
Bientôt disponible
Bordures et lignes
actions.onTableSetCellBorders("#000000")}
>
Bordures sur toutes les cellules
Supprimer les bordures
Couleur des bordures
{DOCS_TABLE_BORDER_COLOR_PRESETS.map((color) => (
actions.onTableSetCellBorders(color)}
>
{color}
))}
actions.onTableSetRowHeight("32px")}
>
Hauteur de ligne compacte
actions.onTableSetRowHeight("48px")}
>
Hauteur de ligne normale
actions.onTableSetRowHeight(null)}
>
Hauteur de ligne automatique
Supprimer la mise en forme
{modShortcut("\\")}
paragraphStylesCtx?.createUserStyle(input)}
/>
)
}