feat: add text alignment (#667)

* feat: text alignment

* fix text case

---------

Co-authored-by: Philipinho <16838612+Philipinho@users.noreply.github.com>
This commit is contained in:
fuscodev 2025-02-06 17:24:36 +01:00 committed by GitHub
parent 638b811857
commit de0b5f0046
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 131 additions and 0 deletions

View File

@ -244,6 +244,7 @@
"Align left": "Align left",
"Align right": "Align right",
"Align center": "Align center",
"Justify": "Justify",
"Merge cells": "Merge cells",
"Split cell": "Split cell",
"Delete column": "Delete column",

View File

@ -18,6 +18,7 @@ import classes from "./bubble-menu.module.css";
import { ActionIcon, rem, Tooltip } from "@mantine/core";
import { ColorSelector } from "./color-selector";
import { NodeSelector } from "./node-selector";
import { TextAlignmentSelector } from "./text-alignment-selector";
import {
draftCommentIdAtom,
showCommentPopupAtom,
@ -117,6 +118,7 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props) => {
moveTransition: "transform 0.15s ease-out",
onHide: () => {
setIsNodeSelectorOpen(false);
setIsTextAlignmentOpen(false);
setIsColorSelectorOpen(false);
setIsLinkSelectorOpen(false);
},
@ -124,6 +126,7 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props) => {
};
const [isNodeSelectorOpen, setIsNodeSelectorOpen] = useState(false);
const [isTextAlignmentSelectorOpen, setIsTextAlignmentOpen] = useState(false);
const [isColorSelectorOpen, setIsColorSelectorOpen] = useState(false);
const [isLinkSelectorOpen, setIsLinkSelectorOpen] = useState(false);
@ -135,6 +138,20 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props) => {
isOpen={isNodeSelectorOpen}
setIsOpen={() => {
setIsNodeSelectorOpen(!isNodeSelectorOpen);
setIsTextAlignmentOpen(false);
setIsColorSelectorOpen(false);
setIsLinkSelectorOpen(false);
}}
/>
<TextAlignmentSelector
editor={props.editor}
isOpen={isTextAlignmentSelectorOpen}
setIsOpen={() => {
setIsTextAlignmentOpen(!isTextAlignmentSelectorOpen);
setIsNodeSelectorOpen(false);
setIsColorSelectorOpen(false);
setIsLinkSelectorOpen(false);
}}
/>
@ -162,6 +179,9 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props) => {
isOpen={isLinkSelectorOpen}
setIsOpen={() => {
setIsLinkSelectorOpen(!isLinkSelectorOpen);
setIsNodeSelectorOpen(false);
setIsTextAlignmentOpen(false);
setIsColorSelectorOpen(false);
}}
/>
@ -170,6 +190,9 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props) => {
isOpen={isColorSelectorOpen}
setIsOpen={() => {
setIsColorSelectorOpen(!isColorSelectorOpen);
setIsNodeSelectorOpen(false);
setIsTextAlignmentOpen(false);
setIsLinkSelectorOpen(false);
}}
/>

View File

@ -0,0 +1,107 @@
import React, { Dispatch, FC, SetStateAction } from "react";
import {
IconAlignCenter,
IconAlignJustified,
IconAlignLeft,
IconAlignRight,
IconCheck,
IconChevronDown,
} from "@tabler/icons-react";
import { Popover, Button, ScrollArea, rem } from "@mantine/core";
import { useEditor } from "@tiptap/react";
import { useTranslation } from "react-i18next";
interface TextAlignmentProps {
editor: ReturnType<typeof useEditor>;
isOpen: boolean;
setIsOpen: Dispatch<SetStateAction<boolean>>;
}
export interface BubbleMenuItem {
name: string;
icon: React.ElementType;
command: () => void;
isActive: () => boolean;
}
export const TextAlignmentSelector: FC<TextAlignmentProps> = ({
editor,
isOpen,
setIsOpen,
}) => {
const { t } = useTranslation();
const items: BubbleMenuItem[] = [
{
name: "Align left",
isActive: () => editor.isActive({ textAlign: "left" }),
command: () => editor.chain().focus().setTextAlign("left").run(),
icon: IconAlignLeft,
},
{
name: "Align center",
isActive: () => editor.isActive({ textAlign: "center" }),
command: () => editor.chain().focus().setTextAlign("center").run(),
icon: IconAlignCenter,
},
{
name: "Align right",
isActive: () => editor.isActive({ textAlign: "right" }),
command: () => editor.chain().focus().setTextAlign("right").run(),
icon: IconAlignRight,
},
{
name: "Justify",
isActive: () => editor.isActive({ textAlign: "justify" }),
command: () => editor.chain().focus().setTextAlign("justify").run(),
icon: IconAlignJustified,
},
];
const activeItem = items.filter((item) => item.isActive()).pop() ?? {
name: "Multiple",
};
return (
<Popover opened={isOpen} withArrow>
<Popover.Target>
<Button
variant="default"
style={{ border: "none", height: "34px" }}
px="5"
radius="0"
rightSection={<IconChevronDown size={16} />}
onClick={() => setIsOpen(!isOpen)}
>
<IconAlignLeft style={{ width: rem(16) }} stroke={2} />
</Button>
</Popover.Target>
<Popover.Dropdown>
<ScrollArea.Autosize type="scroll" mah={400}>
<Button.Group orientation="vertical">
{items.map((item, index) => (
<Button
key={index}
variant="default"
leftSection={<item.icon size={16} />}
rightSection={
activeItem.name === item.name && <IconCheck size={16} />
}
justify="left"
fullWidth
onClick={() => {
item.command();
setIsOpen(false);
}}
style={{ border: "none" }}
>
{t(item.name)}
</Button>
))}
</Button.Group>
</ScrollArea.Autosize>
</Popover.Dropdown>
</Popover>
);
};