1082 lines
40 KiB
TypeScript
1082 lines
40 KiB
TypeScript
"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 <span className="docs-menu-item-icon">{children}</span>
|
|
}
|
|
|
|
function FormatShortcut({ children }: { children: ReactNode }) {
|
|
return <MenubarShortcut>{children}</MenubarShortcut>
|
|
}
|
|
|
|
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 (
|
|
<MenubarItem
|
|
className={cn("docs-menu-item relative pl-8", className)}
|
|
disabled={disabled}
|
|
onClick={onSelect}
|
|
>
|
|
{checked ? <Check className="absolute left-2 size-4" aria-hidden /> : null}
|
|
{children}
|
|
</MenubarItem>
|
|
)
|
|
}
|
|
|
|
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 (
|
|
<DocsExclusiveMenuSub id={`style-${styleId}`}>
|
|
<MenubarSubTrigger className="docs-menu-item" disabled={disabled || menuDisabled}>
|
|
{label}
|
|
</MenubarSubTrigger>
|
|
<MenubarSubContent
|
|
className="docs-menu-content docs-menu-sub-content min-w-[320px] overflow-visible"
|
|
data-docs-menu-surface
|
|
>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={disabled || menuDisabled}
|
|
onClick={() => onApply(styleId)}
|
|
>
|
|
Appliquer le style {label}
|
|
</MenubarItem>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={disabled || menuDisabled}
|
|
onClick={() => onUpdate(styleId)}
|
|
>
|
|
Mettre à jour "{label}" en fonction de la sélection/l'emplacement du
|
|
signe d'insertion
|
|
</MenubarItem>
|
|
</MenubarSubContent>
|
|
</DocsExclusiveMenuSub>
|
|
)
|
|
}
|
|
|
|
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 (
|
|
<MenubarMenu>
|
|
<MenubarTrigger className="docs-menu-trigger">Format</MenubarTrigger>
|
|
<MenubarContent
|
|
{...DOCS_MENUBAR_CONTENT_PROPS}
|
|
className="docs-menu-content min-w-[320px] overflow-visible"
|
|
data-docs-menu-surface
|
|
>
|
|
<DocsExclusiveMenuSubRoot>
|
|
<DocsExclusiveMenuSub id="text">
|
|
<MenubarSubTrigger className="docs-menu-item" disabled={disabled}>
|
|
<MenuIcon>
|
|
<Bold className="size-4" />
|
|
</MenuIcon>
|
|
Texte
|
|
</MenubarSubTrigger>
|
|
<MenubarSubContent
|
|
className="docs-menu-content docs-menu-sub-content min-w-[280px] overflow-visible"
|
|
data-docs-menu-surface
|
|
>
|
|
<MenubarItem className="docs-menu-item" disabled={disabled} onClick={actions.onBold}>
|
|
<MenuIcon>
|
|
<Bold className="size-4" />
|
|
</MenuIcon>
|
|
Gras
|
|
<FormatShortcut>{modShortcut("B")}</FormatShortcut>
|
|
</MenubarItem>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={disabled}
|
|
onClick={actions.onItalic}
|
|
>
|
|
<MenuIcon>
|
|
<Italic className="size-4" />
|
|
</MenuIcon>
|
|
Italique
|
|
<FormatShortcut>{modShortcut("I")}</FormatShortcut>
|
|
</MenubarItem>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={disabled}
|
|
onClick={actions.onUnderline}
|
|
>
|
|
<MenuIcon>
|
|
<Underline className="size-4" />
|
|
</MenuIcon>
|
|
Souligner
|
|
<FormatShortcut>{modShortcut("U")}</FormatShortcut>
|
|
</MenubarItem>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={disabled}
|
|
onClick={actions.onStrike}
|
|
>
|
|
<MenuIcon>
|
|
<Strikethrough className="size-4" />
|
|
</MenuIcon>
|
|
Barrer
|
|
<FormatShortcut>{modShortcut("X", { shift: true })}</FormatShortcut>
|
|
</MenubarItem>
|
|
<MenubarItem className="docs-menu-item" disabled>
|
|
<MenuIcon>
|
|
<span className="text-[11px] font-semibold tracking-tight">Sc</span>
|
|
</MenuIcon>
|
|
Petites majuscules
|
|
<FormatShortcut>{isMacPlatform() ? "⌥+Maj+K" : "Alt+Shift+K"}</FormatShortcut>
|
|
</MenubarItem>
|
|
<MenubarItem className="docs-menu-item" disabled>
|
|
<MenuIcon>
|
|
<span className="text-xs leading-none">
|
|
X<sup className="text-[9px]">2</sup>
|
|
</span>
|
|
</MenuIcon>
|
|
Exposant
|
|
<FormatShortcut>{modShortcut(".")}</FormatShortcut>
|
|
</MenubarItem>
|
|
<MenubarItem className="docs-menu-item" disabled>
|
|
<MenuIcon>
|
|
<span className="text-xs leading-none">
|
|
X<sub className="text-[9px]">2</sub>
|
|
</span>
|
|
</MenuIcon>
|
|
Indice
|
|
<FormatShortcut>{modShortcut(",")}</FormatShortcut>
|
|
</MenubarItem>
|
|
|
|
<MenubarSeparator />
|
|
|
|
<DocsExclusiveMenuSub id="text-size">
|
|
<MenubarSubTrigger className="docs-menu-item" disabled>
|
|
Taille
|
|
</MenubarSubTrigger>
|
|
<MenubarSubContent
|
|
className="docs-menu-content docs-menu-sub-content min-w-[200px] overflow-visible"
|
|
data-docs-menu-surface
|
|
>
|
|
<MenubarItem disabled className="docs-menu-item text-muted-foreground">
|
|
Bientôt disponible
|
|
</MenubarItem>
|
|
</MenubarSubContent>
|
|
</DocsExclusiveMenuSub>
|
|
|
|
<DocsExclusiveMenuSub id="text-case">
|
|
<MenubarSubTrigger className="docs-menu-item" disabled>
|
|
Minuscules/majuscules
|
|
</MenubarSubTrigger>
|
|
<MenubarSubContent
|
|
className="docs-menu-content docs-menu-sub-content min-w-[240px] overflow-visible"
|
|
data-docs-menu-surface
|
|
>
|
|
<MenubarItem disabled className="docs-menu-item text-muted-foreground">
|
|
Bientôt disponible
|
|
</MenubarItem>
|
|
</MenubarSubContent>
|
|
</DocsExclusiveMenuSub>
|
|
</MenubarSubContent>
|
|
</DocsExclusiveMenuSub>
|
|
|
|
<DocsExclusiveMenuSub id="paragraph-styles">
|
|
<MenubarSubTrigger className="docs-menu-item" disabled={disabled}>
|
|
<MenuIcon>
|
|
<Icon icon="material-symbols:format-paragraph" className="size-4" />
|
|
</MenuIcon>
|
|
Styles de paragraphe
|
|
</MenubarSubTrigger>
|
|
<MenubarSubContent
|
|
className="docs-menu-content docs-menu-sub-content min-w-[240px] overflow-visible"
|
|
data-docs-menu-surface
|
|
>
|
|
<MenubarItem className="docs-menu-item" disabled>
|
|
Bordures et ombrage
|
|
</MenubarItem>
|
|
|
|
<MenubarSeparator />
|
|
|
|
{styleEntries
|
|
.filter((entry) => entry.section === "document")
|
|
.map(({ definition }) => (
|
|
<ParagraphStyleSubmenu
|
|
key={definition.id}
|
|
styleId={definition.id}
|
|
label={definition.name}
|
|
menuDisabled={disabled}
|
|
onApply={actions.onApplyStyle}
|
|
onUpdate={
|
|
paragraphStylesCtx?.updateStyleFromSelection ??
|
|
(() => undefined)
|
|
}
|
|
/>
|
|
))}
|
|
|
|
{styleEntries.some((entry) => entry.section === "user") ? (
|
|
<>
|
|
<MenubarSeparator />
|
|
<div className="px-3 py-1.5 text-xs font-medium text-[#5f6368] dark:text-muted-foreground">
|
|
Mes styles
|
|
</div>
|
|
{styleEntries
|
|
.filter((entry) => entry.section === "user")
|
|
.map(({ definition }) => (
|
|
<ParagraphStyleSubmenu
|
|
key={`user-${definition.id}`}
|
|
styleId={definition.id}
|
|
label={definition.name}
|
|
menuDisabled={disabled}
|
|
onApply={actions.onApplyStyle}
|
|
onUpdate={
|
|
paragraphStylesCtx?.updateStyleFromSelection ??
|
|
(() => undefined)
|
|
}
|
|
/>
|
|
))}
|
|
</>
|
|
) : null}
|
|
|
|
<MenubarSeparator />
|
|
|
|
<DocsExclusiveMenuSub id="paragraph-style-options">
|
|
<MenubarSubTrigger className="docs-menu-item" disabled={disabled}>
|
|
Options
|
|
</MenubarSubTrigger>
|
|
<MenubarSubContent
|
|
className="docs-menu-content docs-menu-sub-content min-w-[220px] overflow-visible"
|
|
data-docs-menu-surface
|
|
>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={disabled}
|
|
onClick={() => setCreateStyleOpen(true)}
|
|
>
|
|
Créer un style
|
|
</MenubarItem>
|
|
</MenubarSubContent>
|
|
</DocsExclusiveMenuSub>
|
|
</MenubarSubContent>
|
|
</DocsExclusiveMenuSub>
|
|
|
|
<DocsExclusiveMenuSub id="align-indent">
|
|
<MenubarSubTrigger className="docs-menu-item" disabled={disabled}>
|
|
<MenuIcon>
|
|
<AlignLeft className="size-4" />
|
|
</MenuIcon>
|
|
Aligner et mettre en retrait
|
|
</MenubarSubTrigger>
|
|
<MenubarSubContent
|
|
className="docs-menu-content docs-menu-sub-content min-w-[300px] overflow-visible"
|
|
data-docs-menu-surface
|
|
>
|
|
<CheckMenuOption
|
|
checked={state.alignLeft}
|
|
disabled={disabled}
|
|
onSelect={actions.onAlignLeft}
|
|
>
|
|
<MenuIcon>
|
|
<AlignLeft className="size-4" />
|
|
</MenuIcon>
|
|
À gauche
|
|
<FormatShortcut>{modShortcut("L", { shift: true })}</FormatShortcut>
|
|
</CheckMenuOption>
|
|
<CheckMenuOption
|
|
checked={state.alignCenter}
|
|
disabled={disabled}
|
|
onSelect={actions.onAlignCenter}
|
|
>
|
|
<MenuIcon>
|
|
<AlignCenter className="size-4" />
|
|
</MenuIcon>
|
|
Centrer
|
|
<FormatShortcut>{modShortcut("E", { shift: true })}</FormatShortcut>
|
|
</CheckMenuOption>
|
|
<CheckMenuOption
|
|
checked={state.alignRight}
|
|
disabled={disabled}
|
|
onSelect={actions.onAlignRight}
|
|
>
|
|
<MenuIcon>
|
|
<AlignRight className="size-4" />
|
|
</MenuIcon>
|
|
À droite
|
|
<FormatShortcut>{modShortcut("R", { shift: true })}</FormatShortcut>
|
|
</CheckMenuOption>
|
|
<CheckMenuOption
|
|
checked={state.alignJustify}
|
|
disabled={disabled}
|
|
onSelect={actions.onAlignJustify}
|
|
>
|
|
<MenuIcon>
|
|
<AlignJustify className="size-4" />
|
|
</MenuIcon>
|
|
Justifié
|
|
<FormatShortcut>{modShortcut("J", { shift: true })}</FormatShortcut>
|
|
</CheckMenuOption>
|
|
|
|
<MenubarSeparator />
|
|
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={disabled}
|
|
onClick={actions.onIncreaseIndent}
|
|
>
|
|
<MenuIcon>
|
|
<Icon icon="material-symbols:format-indent-increase" className="size-4" />
|
|
</MenuIcon>
|
|
Augmenter le retrait
|
|
<FormatShortcut>{modShortcut("]")}</FormatShortcut>
|
|
</MenubarItem>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={disabled}
|
|
onClick={actions.onDecreaseIndent}
|
|
>
|
|
<MenuIcon>
|
|
<Icon icon="material-symbols:format-indent-decrease" className="size-4" />
|
|
</MenuIcon>
|
|
Diminuer le retrait
|
|
<FormatShortcut>{modShortcut("[")}</FormatShortcut>
|
|
</MenubarItem>
|
|
<MenubarItem className="docs-menu-item" disabled>
|
|
Options de mise en retrait
|
|
</MenubarItem>
|
|
</MenubarSubContent>
|
|
</DocsExclusiveMenuSub>
|
|
|
|
<DocsExclusiveMenuSub id="line-spacing">
|
|
<MenubarSubTrigger className="docs-menu-item" disabled={disabled}>
|
|
<MenuIcon>
|
|
<Icon icon="material-symbols:format-line-spacing" className="size-4" />
|
|
</MenuIcon>
|
|
Interligne et espace entre paragraphes
|
|
</MenubarSubTrigger>
|
|
<MenubarSubContent
|
|
className="docs-menu-content docs-menu-sub-content min-w-[320px] overflow-visible"
|
|
data-docs-menu-surface
|
|
>
|
|
{LINE_SPACING_OPTIONS.map((option) => (
|
|
<CheckMenuOption
|
|
key={option.id}
|
|
checked={
|
|
state.lineHeightPresetId !== "mixed" && state.lineHeightPresetId === option.id
|
|
}
|
|
disabled={disabled}
|
|
onSelect={() => actions.onSetLineHeight(option.value)}
|
|
>
|
|
{option.label}
|
|
</CheckMenuOption>
|
|
))}
|
|
|
|
<MenubarSeparator />
|
|
|
|
<CheckMenuOption
|
|
checked={state.hasSpaceBefore === true}
|
|
disabled={disabled}
|
|
onSelect={actions.onToggleSpaceBefore}
|
|
>
|
|
Insérer un espacement avant le paragraphe
|
|
</CheckMenuOption>
|
|
<CheckMenuOption
|
|
checked={state.hasSpaceAfter === true}
|
|
disabled={disabled}
|
|
onSelect={actions.onToggleSpaceAfter}
|
|
>
|
|
Insérer un espacement après le paragraphe
|
|
</CheckMenuOption>
|
|
|
|
<MenubarSeparator />
|
|
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={disabled}
|
|
onClick={() => setCustomSpacingOpen(true)}
|
|
>
|
|
Espacement personnalisé
|
|
</MenubarItem>
|
|
|
|
<MenubarSeparator />
|
|
|
|
<CheckMenuOption
|
|
checked={state.keepWithNext === true}
|
|
disabled={disabled}
|
|
onSelect={actions.onToggleKeepWithNext}
|
|
>
|
|
Rattacher au paragraphe suivant
|
|
</CheckMenuOption>
|
|
<CheckMenuOption
|
|
checked={state.keepLinesTogether === true}
|
|
disabled={disabled}
|
|
onSelect={actions.onToggleKeepLinesTogether}
|
|
>
|
|
Ne pas laisser de ligne isolée
|
|
</CheckMenuOption>
|
|
<CheckMenuOption
|
|
checked={state.preventWidowOrphan === true}
|
|
disabled={disabled}
|
|
onSelect={actions.onTogglePreventWidowOrphan}
|
|
>
|
|
Empêcher les lignes isolées
|
|
</CheckMenuOption>
|
|
<CheckMenuOption
|
|
checked={state.pageBreakBefore === true}
|
|
disabled={disabled}
|
|
onSelect={actions.onTogglePageBreakBefore}
|
|
>
|
|
Ajouter un saut de page avant
|
|
</CheckMenuOption>
|
|
</MenubarSubContent>
|
|
</DocsExclusiveMenuSub>
|
|
|
|
<DocsExclusiveMenuSub id="columns">
|
|
<MenubarSubTrigger className="docs-menu-item" disabled={disabled}>
|
|
<MenuIcon>
|
|
<Icon icon="material-symbols:view-column" className="size-4" />
|
|
</MenuIcon>
|
|
Colonnes
|
|
</MenubarSubTrigger>
|
|
<MenubarSubContent
|
|
className="docs-menu-content docs-menu-sub-content min-w-[220px] overflow-visible p-2"
|
|
data-docs-menu-surface
|
|
>
|
|
<DocsFormatColumnPresets disabled />
|
|
<MenubarSeparator className="my-2" />
|
|
<MenubarItem className="docs-menu-item" disabled>
|
|
Autres options
|
|
</MenubarItem>
|
|
</MenubarSubContent>
|
|
</DocsExclusiveMenuSub>
|
|
|
|
<DocsExclusiveMenuSub id="lists">
|
|
<MenubarSubTrigger className="docs-menu-item" disabled={disabled}>
|
|
<MenuIcon>
|
|
<List className="size-4" />
|
|
</MenuIcon>
|
|
Puces et numéros
|
|
</MenubarSubTrigger>
|
|
<MenubarSubContent
|
|
className="docs-menu-content docs-menu-sub-content min-w-[240px] overflow-visible"
|
|
data-docs-menu-surface
|
|
>
|
|
<DocsExclusiveMenuSub id="list-options">
|
|
<MenubarSubTrigger
|
|
className="docs-menu-item"
|
|
disabled={disabled || (!state.isOrderedList && !state.isBulletList && !state.isTaskList)}
|
|
>
|
|
Options de liste
|
|
</MenubarSubTrigger>
|
|
<MenubarSubContent
|
|
className="docs-menu-content docs-menu-sub-content min-w-[280px] overflow-visible"
|
|
data-docs-menu-surface
|
|
>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={disabled || !state.isOrderedList}
|
|
onClick={actions.onRestartOrderedList}
|
|
>
|
|
Recommencer la numérotation
|
|
</MenubarItem>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={disabled || !state.isOrderedList}
|
|
onClick={actions.onContinueOrderedList}
|
|
>
|
|
Continuer la numérotation précédente
|
|
</MenubarItem>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={disabled || !state.isOrderedList}
|
|
onClick={() => setListStartOpen(true)}
|
|
>
|
|
Modifier le numéro de départ…
|
|
</MenubarItem>
|
|
</MenubarSubContent>
|
|
</DocsExclusiveMenuSub>
|
|
|
|
<DocsExclusiveMenuSub id="ordered-list">
|
|
<MenubarSubTrigger className="docs-menu-item" disabled={disabled}>
|
|
<MenuIcon>
|
|
<ListOrdered className="size-4" />
|
|
</MenuIcon>
|
|
Liste numérotée
|
|
</MenubarSubTrigger>
|
|
<MenubarSubContent
|
|
className="docs-menu-content docs-menu-sub-content overflow-visible p-2"
|
|
data-docs-menu-surface
|
|
>
|
|
<DocsFormatNumberedPresets
|
|
disabled={disabled}
|
|
activeStyleId={state.isOrderedList ? state.orderedStyleId : null}
|
|
onSelect={actions.onApplyOrderedStyle}
|
|
/>
|
|
</MenubarSubContent>
|
|
</DocsExclusiveMenuSub>
|
|
|
|
<DocsExclusiveMenuSub id="bullet-list">
|
|
<MenubarSubTrigger className="docs-menu-item" disabled={disabled}>
|
|
<MenuIcon>
|
|
<List className="size-4" />
|
|
</MenuIcon>
|
|
Liste à puces
|
|
</MenubarSubTrigger>
|
|
<MenubarSubContent
|
|
className="docs-menu-content docs-menu-sub-content overflow-visible p-2"
|
|
data-docs-menu-surface
|
|
>
|
|
<DocsFormatBulletPresets
|
|
disabled={disabled}
|
|
activeStyleId={state.isBulletList ? state.bulletStyleId : null}
|
|
onSelect={actions.onApplyBulletStyle}
|
|
/>
|
|
</MenubarSubContent>
|
|
</DocsExclusiveMenuSub>
|
|
|
|
<DocsExclusiveMenuSub id="checklist">
|
|
<MenubarSubTrigger className="docs-menu-item" disabled={disabled}>
|
|
<MenuIcon>
|
|
<Icon icon="material-symbols:checklist" className="size-4" />
|
|
</MenuIcon>
|
|
Menu de type checklist
|
|
</MenubarSubTrigger>
|
|
<MenubarSubContent
|
|
className="docs-menu-content docs-menu-sub-content overflow-visible p-2"
|
|
data-docs-menu-surface
|
|
>
|
|
<DocsFormatChecklistPresets
|
|
disabled={disabled}
|
|
activeStyleId={state.isTaskList ? state.checklistStyleId : null}
|
|
onSelect={actions.onApplyChecklistStyle}
|
|
/>
|
|
</MenubarSubContent>
|
|
</DocsExclusiveMenuSub>
|
|
</MenubarSubContent>
|
|
</DocsExclusiveMenuSub>
|
|
</DocsExclusiveMenuSubRoot>
|
|
|
|
<MenubarSeparator />
|
|
|
|
<MenubarItem className="docs-menu-item" disabled>
|
|
<MenuIcon>
|
|
<Icon icon="material-symbols:page-header" className="size-4" />
|
|
</MenuIcon>
|
|
En-têtes et pieds de page
|
|
</MenubarItem>
|
|
<MenubarItem className="docs-menu-item" disabled>
|
|
<MenuIcon>
|
|
<Icon icon="material-symbols:counter-1" className="size-4" />
|
|
</MenuIcon>
|
|
Numéros de page
|
|
</MenubarItem>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={disabled || !actions.onPageSetup}
|
|
onClick={actions.onPageSetup}
|
|
>
|
|
<MenuIcon>
|
|
<Icon icon="material-symbols:screen-rotation" className="size-4" />
|
|
</MenuIcon>
|
|
Orientation de la page
|
|
</MenubarItem>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={disabled || !actions.onPageSetup}
|
|
onClick={actions.onPageSetup}
|
|
>
|
|
<MenuIcon>
|
|
<Icon icon="material-symbols:select" className="size-4" />
|
|
</MenuIcon>
|
|
Passer au format Sans pages
|
|
</MenubarItem>
|
|
|
|
<MenubarSeparator />
|
|
|
|
<DocsExclusiveMenuSub id="table-format">
|
|
<MenubarSubTrigger
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected}
|
|
>
|
|
<MenuIcon>
|
|
<Icon icon="material-symbols:table" className="size-4" />
|
|
</MenuIcon>
|
|
Tableau
|
|
</MenubarSubTrigger>
|
|
<MenubarSubContent
|
|
className="docs-menu-content docs-menu-sub-content min-w-[260px] overflow-visible"
|
|
data-docs-menu-surface
|
|
>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected}
|
|
onClick={actions.onTableAddRowBefore}
|
|
>
|
|
Insérer une ligne au-dessus
|
|
</MenubarItem>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected}
|
|
onClick={actions.onTableAddRowAfter}
|
|
>
|
|
Insérer une ligne en dessous
|
|
</MenubarItem>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected}
|
|
onClick={actions.onTableAddColumnBefore}
|
|
>
|
|
Insérer une colonne à gauche
|
|
</MenubarItem>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected}
|
|
onClick={actions.onTableAddColumnAfter}
|
|
>
|
|
Insérer une colonne à droite
|
|
</MenubarItem>
|
|
<MenubarSeparator />
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected || !state.tableCanMerge}
|
|
onClick={actions.onTableMergeCells}
|
|
>
|
|
Fusionner les cellules
|
|
</MenubarItem>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected || !state.tableCanSplit}
|
|
onClick={actions.onTableSplitCell}
|
|
>
|
|
Scinder la cellule
|
|
</MenubarItem>
|
|
<MenubarSeparator />
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected}
|
|
onClick={actions.onTableToggleHeaderRow}
|
|
>
|
|
Ligne d'en-tête
|
|
</MenubarItem>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected}
|
|
onClick={actions.onTableToggleHeaderColumn}
|
|
>
|
|
Colonne d'en-tête
|
|
</MenubarItem>
|
|
<MenubarSeparator />
|
|
<DocsExclusiveMenuSub id="table-cell-color">
|
|
<MenubarSubTrigger
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected}
|
|
>
|
|
Couleur de cellule
|
|
</MenubarSubTrigger>
|
|
<MenubarSubContent
|
|
className="docs-menu-content docs-menu-sub-content min-w-[200px] overflow-visible"
|
|
data-docs-menu-surface
|
|
>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected}
|
|
onClick={() => actions.onTableSetCellBackground(null)}
|
|
>
|
|
Aucune
|
|
</MenubarItem>
|
|
{[
|
|
{ label: "Gris clair", color: "#f3f3f3" },
|
|
{ label: "Bleu clair", color: "#c9daf8" },
|
|
{ label: "Vert clair", color: "#d9ead3" },
|
|
{ label: "Jaune clair", color: "#fff2cc" },
|
|
].map((preset) => (
|
|
<MenubarItem
|
|
key={preset.color}
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected}
|
|
onClick={() => actions.onTableSetCellBackground(preset.color)}
|
|
>
|
|
{preset.label}
|
|
</MenubarItem>
|
|
))}
|
|
</MenubarSubContent>
|
|
</DocsExclusiveMenuSub>
|
|
<MenubarSeparator />
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected}
|
|
onClick={actions.onTableAlignLeft}
|
|
>
|
|
Aligner le tableau à gauche
|
|
</MenubarItem>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected}
|
|
onClick={actions.onTableAlignCenter}
|
|
>
|
|
Centrer le tableau
|
|
</MenubarItem>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected}
|
|
onClick={actions.onTableAlignRight}
|
|
>
|
|
Aligner le tableau à droite
|
|
</MenubarItem>
|
|
<MenubarSeparator />
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected}
|
|
onClick={actions.onTableDeleteRow}
|
|
>
|
|
Supprimer la ligne
|
|
</MenubarItem>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected}
|
|
onClick={actions.onTableDeleteColumn}
|
|
>
|
|
Supprimer la colonne
|
|
</MenubarItem>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected}
|
|
onClick={actions.onTableDelete}
|
|
>
|
|
Supprimer le tableau
|
|
</MenubarItem>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected}
|
|
onClick={actions.onTableFixStructure}
|
|
>
|
|
Réparer la structure
|
|
</MenubarItem>
|
|
</MenubarSubContent>
|
|
</DocsExclusiveMenuSub>
|
|
|
|
<DocsExclusiveMenuSub id="image-format">
|
|
<MenubarSubTrigger
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.imageSelected}
|
|
>
|
|
<MenuIcon>
|
|
<Icon icon="material-symbols:image-outline" className="size-4" />
|
|
</MenuIcon>
|
|
Image
|
|
</MenubarSubTrigger>
|
|
<MenubarSubContent
|
|
className="docs-menu-content docs-menu-sub-content min-w-[220px] overflow-visible"
|
|
data-docs-menu-surface
|
|
>
|
|
<MenubarItem disabled className="docs-menu-item text-muted-foreground">
|
|
Bientôt disponible
|
|
</MenubarItem>
|
|
</MenubarSubContent>
|
|
</DocsExclusiveMenuSub>
|
|
|
|
<DocsExclusiveMenuSub id="borders-lines">
|
|
<MenubarSubTrigger
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected}
|
|
>
|
|
<MenuIcon>
|
|
<Icon icon="material-symbols:horizontal-rule" className="size-4" />
|
|
</MenuIcon>
|
|
Bordures et lignes
|
|
</MenubarSubTrigger>
|
|
<MenubarSubContent
|
|
className="docs-menu-content docs-menu-sub-content min-w-[220px] overflow-visible"
|
|
data-docs-menu-surface
|
|
>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected}
|
|
onClick={() => actions.onTableSetCellBorders("#000000")}
|
|
>
|
|
Bordures sur toutes les cellules
|
|
</MenubarItem>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected}
|
|
onClick={actions.onTableClearCellBorders}
|
|
>
|
|
Supprimer les bordures
|
|
</MenubarItem>
|
|
<MenubarSeparator />
|
|
<DocsExclusiveMenuSub id="table-border-color">
|
|
<MenubarSubTrigger
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected}
|
|
>
|
|
Couleur des bordures
|
|
</MenubarSubTrigger>
|
|
<MenubarSubContent
|
|
className="docs-menu-content docs-menu-sub-content min-w-[180px] overflow-visible"
|
|
data-docs-menu-surface
|
|
>
|
|
{DOCS_TABLE_BORDER_COLOR_PRESETS.map((color) => (
|
|
<MenubarItem
|
|
key={color}
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected}
|
|
onClick={() => actions.onTableSetCellBorders(color)}
|
|
>
|
|
<span
|
|
className="mr-2 inline-block size-4 rounded-sm border border-border"
|
|
style={{ backgroundColor: color }}
|
|
aria-hidden
|
|
/>
|
|
{color}
|
|
</MenubarItem>
|
|
))}
|
|
</MenubarSubContent>
|
|
</DocsExclusiveMenuSub>
|
|
<MenubarSeparator />
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected}
|
|
onClick={() => actions.onTableSetRowHeight("32px")}
|
|
>
|
|
Hauteur de ligne compacte
|
|
</MenubarItem>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected}
|
|
onClick={() => actions.onTableSetRowHeight("48px")}
|
|
>
|
|
Hauteur de ligne normale
|
|
</MenubarItem>
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={objectFormattingDisabled || !state.tableSelected}
|
|
onClick={() => actions.onTableSetRowHeight(null)}
|
|
>
|
|
Hauteur de ligne automatique
|
|
</MenubarItem>
|
|
</MenubarSubContent>
|
|
</DocsExclusiveMenuSub>
|
|
|
|
<MenubarSeparator />
|
|
|
|
<MenubarItem
|
|
className="docs-menu-item"
|
|
disabled={disabled}
|
|
onClick={actions.onClearFormatting}
|
|
>
|
|
<MenuIcon>
|
|
<Icon icon="material-symbols:format-clear" className="size-4" />
|
|
</MenuIcon>
|
|
Supprimer la mise en forme
|
|
<FormatShortcut>{modShortcut("\\")}</FormatShortcut>
|
|
</MenubarItem>
|
|
</MenubarContent>
|
|
<DocsCreateStyleDialog
|
|
open={createStyleOpen}
|
|
onOpenChange={setCreateStyleOpen}
|
|
onCreate={(input) => paragraphStylesCtx?.createUserStyle(input)}
|
|
/>
|
|
<DocsLineSpacingDialog
|
|
open={customSpacingOpen}
|
|
onOpenChange={setCustomSpacingOpen}
|
|
initial={state.customSpacingDraft}
|
|
onApply={actions.onApplyCustomSpacing}
|
|
/>
|
|
<DocsListStartDialog
|
|
open={listStartOpen}
|
|
onOpenChange={setListStartOpen}
|
|
initialStart={state.orderedListStart === "mixed" ? 1 : state.orderedListStart}
|
|
onApply={actions.onSetOrderedListStart}
|
|
/>
|
|
</MenubarMenu>
|
|
)
|
|
}
|