Skip to content

Commit

Permalink
Text Align in bubble menu
Browse files Browse the repository at this point in the history
  • Loading branch information
firehudson committed Aug 22, 2023
1 parent 2196990 commit 4d76cd1
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 0 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@tiptap/extension-placeholder": "2.0.3",
"@tiptap/extension-task-item": "^2.0.4",
"@tiptap/extension-task-list": "^2.0.4",
"@tiptap/extension-text-align": "^2.1.6",
"@tiptap/extension-text-style": "^2.0.4",
"@tiptap/extension-underline": "^2.0.4",
"@tiptap/pm": "^2.0.4",
Expand Down
11 changes: 11 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions ui/editor/components/bubble-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { NodeSelector } from "./node-selector";
import { ColorSelector } from "./color-selector";
import { LinkSelector } from "./link-selector";
import { cn } from "@/lib/utils";
import { TextAlignSelector } from "./text-align-selector";

export interface BubbleMenuItem {
name: string;
Expand Down Expand Up @@ -78,6 +79,7 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props) => {
const [isNodeSelectorOpen, setIsNodeSelectorOpen] = useState(false);
const [isColorSelectorOpen, setIsColorSelectorOpen] = useState(false);
const [isLinkSelectorOpen, setIsLinkSelectorOpen] = useState(false);
const [isTextAlignSelectorOpen, setIsTextAlignSelectorOpen] = useState(false);

return (
<BubbleMenu
Expand All @@ -91,6 +93,7 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props) => {
setIsNodeSelectorOpen(!isNodeSelectorOpen);
setIsColorSelectorOpen(false);
setIsLinkSelectorOpen(false);
setIsTextAlignSelectorOpen(false);
}}
/>
<LinkSelector
Expand All @@ -116,14 +119,26 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props) => {
/>
</button>
))}
<TextAlignSelector
editor={props.editor}
isOpen={isTextAlignSelectorOpen}
setIsOpen={() => {
setIsTextAlignSelectorOpen(!isTextAlignSelectorOpen);
setIsColorSelectorOpen(false);
setIsNodeSelectorOpen(false);
setIsLinkSelectorOpen(false);
}}
/>
</div>

<ColorSelector
editor={props.editor}
isOpen={isColorSelectorOpen}
setIsOpen={() => {
setIsColorSelectorOpen(!isColorSelectorOpen);
setIsNodeSelectorOpen(false);
setIsLinkSelectorOpen(false);
setIsTextAlignSelectorOpen(false);
}}
/>
</BubbleMenu>
Expand Down
91 changes: 91 additions & 0 deletions ui/editor/components/text-align-selector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Editor } from "@tiptap/core";
import {
Check,
ChevronDown,
AlignLeft,
AlignCenter,
AlignRight,
AlignJustify,
} from "lucide-react";
import { Dispatch, FC, SetStateAction } from "react";

import { BubbleMenuItem } from "./bubble-menu";

interface TextAlignSelectorProps {
editor: Editor;
isOpen: boolean;
setIsOpen: Dispatch<SetStateAction<boolean>>;
}

export const TextAlignSelector: FC<TextAlignSelectorProps> = ({
editor,
isOpen,
setIsOpen,
}) => {
const items: BubbleMenuItem[] = [
{
name: "Align Left",
icon: AlignLeft,
command: () => editor.chain().focus().setTextAlign("left").run(),
isActive: () => editor.isActive({ textAlign: "left" }),
},
{
name: "Align Center",
icon: AlignCenter,
command: () => editor.chain().focus().setTextAlign("center").run(),
isActive: () => editor.isActive({ textAlign: "center" }),
},
{
name: "Align Right",
icon: AlignRight,
command: () => editor.chain().focus().setTextAlign("right").run(),
isActive: () => editor.isActive({ textAlign: "right" }),
},
{
name: "Align Justify",
icon: AlignJustify,
command: () => editor.chain().focus().setTextAlign("justify").run(),
isActive: () => editor.isActive({ textAlign: "justify" }),
},
];

const activeItem = items.filter((item) => item.isActive()).pop();

return (
<div className="relative h-full">
<button
className="flex h-full items-center gap-1 whitespace-nowrap p-2 text-sm font-medium text-stone-600 hover:bg-stone-100 active:bg-stone-200"
onClick={() => setIsOpen(!isOpen)}
>
<span>
<activeItem.icon className="h-3 w-3" />
</span>
<ChevronDown className="h-4 w-4" />
</button>

{isOpen && (
<section className="fixed top-full z-[99999] mt-1 flex w-20 flex-col overflow-hidden rounded border border-stone-200 bg-white p-1 shadow-xl animate-in fade-in slide-in-from-top-1">
{items.map((item, index) => (
<button
key={index}
onClick={() => {
item.command();
setIsOpen(false);
}}
className="flex items-center justify-between rounded-sm px-2 py-1 text-sm text-stone-600 hover:bg-stone-100"
>
<div className="flex items-center space-x-2">
<div className="rounded-sm border border-stone-200 p-1">
<item.icon className="h-3 w-3" />
</div>
</div>
{activeItem && activeItem.name === item.name && (
<Check className="h-4 w-4" />
)}
</button>
))}
</section>
)}
</div>
);
};
4 changes: 4 additions & 0 deletions ui/editor/extensions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import SlashCommand from "./slash-command";
import { InputRule } from "@tiptap/core";
import UploadImagesPlugin from "@/ui/editor/plugins/upload-images";
import UpdatedImage from "./updated-image";
import TextAlign from "@tiptap/extension-text-align";

const CustomImage = TiptapImage.extend({
addProseMirrorPlugins() {
Expand Down Expand Up @@ -137,4 +138,7 @@ export const TiptapExtensions = [
html: false,
transformCopiedText: true,
}),
TextAlign.configure({
types: ["heading", "paragraph"],
}),
];

0 comments on commit 4d76cd1

Please sign in to comment.