mirror of
https://github.com/docmost/docmost.git
synced 2025-11-19 03:41:10 +10:00
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:
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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}`,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
24
packages/editor-ext/src/lib/table/header.ts
Normal file
24
packages/editor-ext/src/lib/table/header.ts
Normal 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}`,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
@ -1,2 +1,3 @@
|
||||
export * from "./row";
|
||||
export * from "./cell";
|
||||
export * from "./header";
|
||||
|
||||
Reference in New Issue
Block a user