mirror of
https://github.com/docmost/docmost.git
synced 2025-11-13 15:22:40 +10:00
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:
@ -244,6 +244,7 @@
|
|||||||
"Align left": "Align left",
|
"Align left": "Align left",
|
||||||
"Align right": "Align right",
|
"Align right": "Align right",
|
||||||
"Align center": "Align center",
|
"Align center": "Align center",
|
||||||
|
"Justify": "Justify",
|
||||||
"Merge cells": "Merge cells",
|
"Merge cells": "Merge cells",
|
||||||
"Split cell": "Split cell",
|
"Split cell": "Split cell",
|
||||||
"Delete column": "Delete column",
|
"Delete column": "Delete column",
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import classes from "./bubble-menu.module.css";
|
|||||||
import { ActionIcon, rem, Tooltip } from "@mantine/core";
|
import { ActionIcon, rem, Tooltip } from "@mantine/core";
|
||||||
import { ColorSelector } from "./color-selector";
|
import { ColorSelector } from "./color-selector";
|
||||||
import { NodeSelector } from "./node-selector";
|
import { NodeSelector } from "./node-selector";
|
||||||
|
import { TextAlignmentSelector } from "./text-alignment-selector";
|
||||||
import {
|
import {
|
||||||
draftCommentIdAtom,
|
draftCommentIdAtom,
|
||||||
showCommentPopupAtom,
|
showCommentPopupAtom,
|
||||||
@ -117,6 +118,7 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props) => {
|
|||||||
moveTransition: "transform 0.15s ease-out",
|
moveTransition: "transform 0.15s ease-out",
|
||||||
onHide: () => {
|
onHide: () => {
|
||||||
setIsNodeSelectorOpen(false);
|
setIsNodeSelectorOpen(false);
|
||||||
|
setIsTextAlignmentOpen(false);
|
||||||
setIsColorSelectorOpen(false);
|
setIsColorSelectorOpen(false);
|
||||||
setIsLinkSelectorOpen(false);
|
setIsLinkSelectorOpen(false);
|
||||||
},
|
},
|
||||||
@ -124,6 +126,7 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const [isNodeSelectorOpen, setIsNodeSelectorOpen] = useState(false);
|
const [isNodeSelectorOpen, setIsNodeSelectorOpen] = useState(false);
|
||||||
|
const [isTextAlignmentSelectorOpen, setIsTextAlignmentOpen] = useState(false);
|
||||||
const [isColorSelectorOpen, setIsColorSelectorOpen] = useState(false);
|
const [isColorSelectorOpen, setIsColorSelectorOpen] = useState(false);
|
||||||
const [isLinkSelectorOpen, setIsLinkSelectorOpen] = useState(false);
|
const [isLinkSelectorOpen, setIsLinkSelectorOpen] = useState(false);
|
||||||
|
|
||||||
@ -135,6 +138,20 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props) => {
|
|||||||
isOpen={isNodeSelectorOpen}
|
isOpen={isNodeSelectorOpen}
|
||||||
setIsOpen={() => {
|
setIsOpen={() => {
|
||||||
setIsNodeSelectorOpen(!isNodeSelectorOpen);
|
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}
|
isOpen={isLinkSelectorOpen}
|
||||||
setIsOpen={() => {
|
setIsOpen={() => {
|
||||||
setIsLinkSelectorOpen(!isLinkSelectorOpen);
|
setIsLinkSelectorOpen(!isLinkSelectorOpen);
|
||||||
|
setIsNodeSelectorOpen(false);
|
||||||
|
setIsTextAlignmentOpen(false);
|
||||||
|
setIsColorSelectorOpen(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -170,6 +190,9 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props) => {
|
|||||||
isOpen={isColorSelectorOpen}
|
isOpen={isColorSelectorOpen}
|
||||||
setIsOpen={() => {
|
setIsOpen={() => {
|
||||||
setIsColorSelectorOpen(!isColorSelectorOpen);
|
setIsColorSelectorOpen(!isColorSelectorOpen);
|
||||||
|
setIsNodeSelectorOpen(false);
|
||||||
|
setIsTextAlignmentOpen(false);
|
||||||
|
setIsLinkSelectorOpen(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user