text formatting finished
This commit is contained in:
@@ -10,12 +10,15 @@ import {
|
|||||||
Heading1,
|
Heading1,
|
||||||
Heading2,
|
Heading2,
|
||||||
Heading3,
|
Heading3,
|
||||||
|
Heading,
|
||||||
Eraser,
|
Eraser,
|
||||||
ListTodo,
|
ListTodo,
|
||||||
Grid3X3
|
Grid3X3,
|
||||||
|
AlignJustify
|
||||||
} from "lucide-solid";
|
} from "lucide-solid";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import type { Editor } from "@tiptap/core";
|
import type { Editor } from "@tiptap/core";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||||
|
|
||||||
interface GenericAction {
|
interface GenericAction {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -28,6 +31,8 @@ interface GenericAction {
|
|||||||
interface FormatGroup {
|
interface FormatGroup {
|
||||||
name: string;
|
name: string;
|
||||||
actions: GenericAction[];
|
actions: GenericAction[];
|
||||||
|
collapseOnMobile?: boolean;
|
||||||
|
mobileIcon?: Component;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TextBubbleMenu: Component<{ editor: Editor }> = (props) => {
|
export const TextBubbleMenu: Component<{ editor: Editor }> = (props) => {
|
||||||
@@ -76,6 +81,8 @@ export const TextBubbleMenu: Component<{ editor: Editor }> = (props) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Headings",
|
name: "Headings",
|
||||||
|
collapseOnMobile: true,
|
||||||
|
mobileIcon: () => <Heading size={16} />,
|
||||||
actions: [
|
actions: [
|
||||||
{ label: "Heading 1", icon: () => <Heading1 size={16} />, isActive: () => activeFormats().h1, command: () => props.editor.chain().focus().toggleHeading({ level: 1 }).run() },
|
{ label: "Heading 1", icon: () => <Heading1 size={16} />, isActive: () => activeFormats().h1, command: () => props.editor.chain().focus().toggleHeading({ level: 1 }).run() },
|
||||||
{ label: "Heading 2", icon: () => <Heading2 size={16} />, isActive: () => activeFormats().h2, command: () => props.editor.chain().focus().toggleHeading({ level: 2 }).run() },
|
{ label: "Heading 2", icon: () => <Heading2 size={16} />, isActive: () => activeFormats().h2, command: () => props.editor.chain().focus().toggleHeading({ level: 2 }).run() },
|
||||||
@@ -84,6 +91,8 @@ export const TextBubbleMenu: Component<{ editor: Editor }> = (props) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Alignment",
|
name: "Alignment",
|
||||||
|
collapseOnMobile: true,
|
||||||
|
mobileIcon: () => <AlignJustify size={16} />,
|
||||||
actions: [
|
actions: [
|
||||||
{ label: "Align Left", icon: () => <AlignLeft size={16} />, isActive: () => activeFormats().alignLeft, command: () => props.editor.chain().focus().setTextAlign('left').run() },
|
{ label: "Align Left", icon: () => <AlignLeft size={16} />, isActive: () => activeFormats().alignLeft, command: () => props.editor.chain().focus().setTextAlign('left').run() },
|
||||||
{ label: "Align Center", icon: () => <AlignCenter size={16} />, isActive: () => activeFormats().alignCenter, command: () => props.editor.chain().focus().setTextAlign('center').run() },
|
{ label: "Align Center", icon: () => <AlignCenter size={16} />, isActive: () => activeFormats().alignCenter, command: () => props.editor.chain().focus().setTextAlign('center').run() },
|
||||||
@@ -120,7 +129,7 @@ export const TextBubbleMenu: Component<{ editor: Editor }> = (props) => {
|
|||||||
<For each={groups}>
|
<For each={groups}>
|
||||||
{(group, groupIdx) => (
|
{(group, groupIdx) => (
|
||||||
<>
|
<>
|
||||||
<div class="flex items-center gap-0 px-0.5">
|
<div class={cn("flex items-center gap-0 px-0.5", group.collapseOnMobile ? "hidden sm:flex" : "")}>
|
||||||
<For each={group.actions}>
|
<For each={group.actions}>
|
||||||
{(action) => (
|
{(action) => (
|
||||||
<button
|
<button
|
||||||
@@ -137,6 +146,7 @@ export const TextBubbleMenu: Component<{ editor: Editor }> = (props) => {
|
|||||||
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
||||||
action.iconClass
|
action.iconClass
|
||||||
)}
|
)}
|
||||||
|
title={action.label}
|
||||||
>
|
>
|
||||||
<action.icon />
|
<action.icon />
|
||||||
<span class="absolute top-full left-1/2 -translate-x-1/2 mt-2 px-2 py-1 text-[10px] font-bold bg-foreground text-background rounded opacity-0 group-hover:opacity-100 pointer-events-none transition-opacity whitespace-nowrap z-[200] shadow-lg">
|
<span class="absolute top-full left-1/2 -translate-x-1/2 mt-2 px-2 py-1 text-[10px] font-bold bg-foreground text-background rounded opacity-0 group-hover:opacity-100 pointer-events-none transition-opacity whitespace-nowrap z-[200] shadow-lg">
|
||||||
@@ -146,6 +156,43 @@ export const TextBubbleMenu: Component<{ editor: Editor }> = (props) => {
|
|||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{group.collapseOnMobile && group.mobileIcon && (
|
||||||
|
<div class="flex sm:hidden items-center gap-0 px-0.5">
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger class={cn(
|
||||||
|
"p-1.5 rounded-md transition-colors group relative shrink-0 text-muted-foreground hover:bg-muted hover:text-foreground",
|
||||||
|
group.actions.some(a => a.isActive?.()) && "bg-primary/20 text-primary"
|
||||||
|
)}>
|
||||||
|
<group.mobileIcon />
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent class="w-auto p-1 flex items-center gap-1 shadow-xl" align="center">
|
||||||
|
<For each={group.actions}>
|
||||||
|
{(action) => (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
action.command();
|
||||||
|
}}
|
||||||
|
class={cn(
|
||||||
|
"p-1.5 rounded-md transition-colors relative shrink-0",
|
||||||
|
action.isActive?.()
|
||||||
|
? "bg-primary text-primary-foreground"
|
||||||
|
: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
||||||
|
action.iconClass
|
||||||
|
)}
|
||||||
|
title={action.label}
|
||||||
|
>
|
||||||
|
<action.icon />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{groupIdx() < groups.length - 1 && (
|
{groupIdx() < groups.length - 1 && (
|
||||||
<div class="w-[1px] h-4 bg-border/50 mx-1" />
|
<div class="w-[1px] h-4 bg-border/50 mx-1" />
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user