feat: add table cell background color picker

- Extended TableCell and TableHeader to support backgroundColor attribute
- Created TableBackgroundColor component with 21 color options
- Integrated color picker into table cell menu using Mantine UI
- Added support for both regular cells and header cells
- Updated imports to use custom TableHeader from @docmost/editor-ext
This commit is contained in:
Philipinho
2025-07-09 14:25:55 -07:00
parent 0d8b6ec4f0
commit 86aaf2f6af
7 changed files with 209 additions and 7 deletions

View File

@ -0,0 +1,156 @@
import React, { FC } from "react";
import { IconCheck, IconPalette } from "@tabler/icons-react";
import {
ActionIcon,
ColorSwatch,
Popover,
Stack,
Text,
Tooltip,
UnstyledButton,
} from "@mantine/core";
import { useEditor } from "@tiptap/react";
import { useTranslation } from "react-i18next";
export interface TableColorItem {
name: string;
color: string;
}
interface TableBackgroundColorProps {
editor: ReturnType<typeof useEditor>;
}
const TABLE_COLORS: TableColorItem[] = [
// First row - grays
{ name: "Default", color: "" },
{ name: "Light blue", color: "#E6E9FF" },
{ name: "Light cyan", color: "#E0F2FE" },
{ name: "Light teal", color: "#CCFBF1" },
{ name: "Light yellow", color: "#FEF3C7" },
{ name: "Light pink", color: "#FCE7F3" },
{ name: "Light purple", color: "#EDE9FE" },
// Second row - light colors
{ name: "Gray", color: "#F3F4F6" },
{ name: "Blue", color: "#BFDBFE" },
{ name: "Cyan", color: "#A5F3FC" },
{ name: "Teal", color: "#99F6E4" },
{ name: "Yellow", color: "#FDE68A" },
{ name: "Pink", color: "#FBCFE8" },
{ name: "Purple", color: "#DDD6FE" },
// Third row - bold colors
{ name: "Dark gray", color: "#9CA3AF" },
{ name: "Bold blue", color: "#60A5FA" },
{ name: "Bold cyan", color: "#22D3EE" },
{ name: "Bold teal", color: "#2DD4BF" },
{ name: "Bold orange", color: "#FB923C" },
{ name: "Bold red", color: "#F87171" },
{ name: "Bold purple", color: "#A78BFA" },
];
export const TableBackgroundColor: FC<TableBackgroundColorProps> = ({
editor,
}) => {
const { t } = useTranslation();
const [opened, setOpened] = React.useState(false);
const setTableCellBackground = (color: string) => {
editor
.chain()
.focus()
.updateAttributes("tableCell", { backgroundColor: color || null })
.updateAttributes("tableHeader", { backgroundColor: color || null })
.run();
setOpened(false);
};
// Get current cell's background color
const getCurrentColor = () => {
if (editor.isActive("tableCell")) {
const attrs = editor.getAttributes("tableCell");
return attrs.backgroundColor || "";
}
if (editor.isActive("tableHeader")) {
const attrs = editor.getAttributes("tableHeader");
return attrs.backgroundColor || "";
}
return "";
};
const currentColor = getCurrentColor();
return (
<Popover
width={280}
position="bottom"
opened={opened}
onChange={setOpened}
withArrow
transitionProps={{ transition: 'pop' }}
>
<Popover.Target>
<Tooltip label={t("Background color")} withArrow>
<ActionIcon
variant="default"
size="lg"
aria-label={t("Background color")}
onClick={() => setOpened(!opened)}
>
<IconPalette size={18} />
</ActionIcon>
</Tooltip>
</Popover.Target>
<Popover.Dropdown>
<Stack gap="xs">
<Text size="sm" c="dimmed">
{t("Background color")}
</Text>
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(7, 1fr)",
gap: "8px",
}}
>
{TABLE_COLORS.map((item, index) => (
<UnstyledButton
key={index}
onClick={() => setTableCellBackground(item.color)}
style={{
position: "relative",
width: "24px",
height: "24px",
}}
title={t(item.name)}
>
<ColorSwatch
color={item.color || "#ffffff"}
size={24}
style={{
border: item.color === "" ? "1px solid #e5e7eb" : undefined,
cursor: "pointer",
}}
>
{currentColor === item.color && (
<IconCheck
size={18}
style={{
color: item.color === "" || item.color.startsWith("#F")
? "#000000"
: "#ffffff",
}}
/>
)}
</ColorSwatch>
</UnstyledButton>
))}
</div>
</Stack>
</Popover.Dropdown>
</Popover>
);
};

View File

@ -15,6 +15,7 @@ import {
IconTableRow,
} from "@tabler/icons-react";
import { useTranslation } from "react-i18next";
import { TableBackgroundColor } from "./table-background-color";
export const TableCellMenu = React.memo(
({ editor, appendTo }: EditorMenuProps): JSX.Element => {
@ -65,6 +66,8 @@ export const TableCellMenu = React.memo(
shouldShow={shouldShow}
>
<ActionIcon.Group>
<TableBackgroundColor editor={editor} />
<Tooltip position="top" label={t("Merge cells")}>
<ActionIcon
onClick={mergeCells}

View File

@ -11,7 +11,6 @@ import { Typography } from "@tiptap/extension-typography";
import { TextStyle } from "@tiptap/extension-text-style";
import { Color } from "@tiptap/extension-color";
import Table from "@tiptap/extension-table";
import TableHeader from "@tiptap/extension-table-header";
import SlashCommand from "@/features/editor/extensions/slash-command";
import { Collaboration } from "@tiptap/extension-collaboration";
import { CollaborationCursor } from "@tiptap/extension-collaboration-cursor";
@ -25,6 +24,7 @@ import {
MathInline,
TableCell,
TableRow,
TableHeader,
TrailingNode,
TiptapImage,
Callout,

View File

@ -11,7 +11,6 @@ import { TextStyle } from '@tiptap/extension-text-style';
import { Color } from '@tiptap/extension-color';
import { Youtube } from '@tiptap/extension-youtube';
import Table from '@tiptap/extension-table';
import TableHeader from '@tiptap/extension-table-header';
import {
Callout,
Comment,
@ -22,6 +21,7 @@ import {
LinkExtension,
MathBlock,
MathInline,
TableHeader,
TableCell,
TableRow,
TiptapImage,
@ -31,7 +31,7 @@ import {
Drawio,
Excalidraw,
Embed,
Mention
Mention,
} from '@docmost/editor-ext';
import { generateText, getSchema, JSONContent } from '@tiptap/core';
import { generateHTML } from '../common/helpers/prosemirror/html';
@ -46,7 +46,7 @@ export const tiptapExtensions = [
codeBlock: false,
}),
Comment,
TextAlign.configure({ types: ["heading", "paragraph"] }),
TextAlign.configure({ types: ['heading', 'paragraph'] }),
TaskList,
TaskItem,
Underline,
@ -64,9 +64,9 @@ export const tiptapExtensions = [
DetailsContent,
DetailsSummary,
Table,
TableHeader,
TableRow,
TableCell,
TableRow,
TableHeader,
Youtube,
TiptapImage,
TiptapVideo,
@ -76,7 +76,7 @@ export const tiptapExtensions = [
Drawio,
Excalidraw,
Embed,
Mention
Mention,
] as any;
export function jsonToHtml(tiptapJson: any) {

View File

@ -3,4 +3,22 @@ import { TableCell as TiptapTableCell } from "@tiptap/extension-table-cell";
export const TableCell = TiptapTableCell.extend({
name: "tableCell",
content: "paragraph+",
addAttributes() {
return {
...this.parent?.(),
backgroundColor: {
default: null,
parseHTML: (element) => element.style.backgroundColor || null,
renderHTML: (attributes) => {
if (!attributes.backgroundColor) {
return {};
}
return {
style: `background-color: ${attributes.backgroundColor}`,
};
},
},
};
},
});

View File

@ -0,0 +1,24 @@
import { TableHeader as TiptapTableHeader } from "@tiptap/extension-table-header";
export const TableHeader = TiptapTableHeader.extend({
name: "tableHeader",
content: "paragraph+",
addAttributes() {
return {
...this.parent?.(),
backgroundColor: {
default: null,
parseHTML: (element) => element.style.backgroundColor || null,
renderHTML: (attributes) => {
if (!attributes.backgroundColor) {
return {};
}
return {
style: `background-color: ${attributes.backgroundColor}`,
};
},
},
};
},
});

View File

@ -1,2 +1,3 @@
export * from "./row";
export * from "./cell";
export * from "./header";