mirror of
https://github.com/docmost/docmost.git
synced 2026-05-17 23:14:07 +08:00
feat: Text background highlight (#1754)
* #1196/feat: add text background highlight * unify text color * dark mode support * unify text color and highlight * dark mode support for color selector trigger * fix see through in color selector dark mode * fix selection highlight in dark mode * brown color * clean up --------- Co-authored-by: sanua356 <sanek.pankratov356@gmail.com>
This commit is contained in:
@@ -144,16 +144,16 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props) => {
|
||||
onHide: () => {
|
||||
setIsNodeSelectorOpen(false);
|
||||
setIsTextAlignmentOpen(false);
|
||||
setIsColorSelectorOpen(false);
|
||||
setIsLinkSelectorOpen(false);
|
||||
setIsColorSelectorOpen(false);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const [isNodeSelectorOpen, setIsNodeSelectorOpen] = useState(false);
|
||||
const [isTextAlignmentSelectorOpen, setIsTextAlignmentOpen] = useState(false);
|
||||
const [isColorSelectorOpen, setIsColorSelectorOpen] = useState(false);
|
||||
const [isLinkSelectorOpen, setIsLinkSelectorOpen] = useState(false);
|
||||
const [isColorSelectorOpen, setIsColorSelectorOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<BubbleMenu {...bubbleMenuProps}>
|
||||
@@ -164,8 +164,8 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props) => {
|
||||
setIsOpen={() => {
|
||||
setIsNodeSelectorOpen(!isNodeSelectorOpen);
|
||||
setIsTextAlignmentOpen(false);
|
||||
setIsColorSelectorOpen(false);
|
||||
setIsLinkSelectorOpen(false);
|
||||
setIsColorSelectorOpen(false);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -175,8 +175,8 @@ export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props) => {
|
||||
setIsOpen={() => {
|
||||
setIsTextAlignmentOpen(!isTextAlignmentSelectorOpen);
|
||||
setIsNodeSelectorOpen(false);
|
||||
setIsColorSelectorOpen(false);
|
||||
setIsLinkSelectorOpen(false);
|
||||
setIsColorSelectorOpen(false);
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Dispatch, FC, SetStateAction } from "react";
|
||||
import { IconCheck, IconPalette } from "@tabler/icons-react";
|
||||
import React, { Dispatch, FC, SetStateAction } from "react";
|
||||
import { IconCheck, IconChevronDown, IconPalette } from "@tabler/icons-react";
|
||||
import {
|
||||
ActionIcon,
|
||||
Button,
|
||||
@@ -8,6 +8,9 @@ import {
|
||||
ScrollArea,
|
||||
Text,
|
||||
Tooltip,
|
||||
SimpleGrid,
|
||||
Box,
|
||||
Stack,
|
||||
} from "@mantine/core";
|
||||
import type { Editor } from "@tiptap/react";
|
||||
import { useEditorState } from "@tiptap/react";
|
||||
@@ -61,9 +64,12 @@ const TEXT_COLORS: BubbleColorMenuItem[] = [
|
||||
name: "Gray",
|
||||
color: "#A8A29E",
|
||||
},
|
||||
{
|
||||
name: "Brown",
|
||||
color: "#92400E",
|
||||
},
|
||||
];
|
||||
|
||||
// TODO: handle dark mode
|
||||
const HIGHLIGHT_COLORS: BubbleColorMenuItem[] = [
|
||||
{
|
||||
name: "Default",
|
||||
@@ -71,35 +77,39 @@ const HIGHLIGHT_COLORS: BubbleColorMenuItem[] = [
|
||||
},
|
||||
{
|
||||
name: "Blue",
|
||||
color: "#c1ecf9",
|
||||
color: "#98d8f2",
|
||||
},
|
||||
{
|
||||
name: "Green",
|
||||
color: "#acf79f",
|
||||
color: "#7edb6c",
|
||||
},
|
||||
{
|
||||
name: "Purple",
|
||||
color: "#f6f3f8",
|
||||
color: "#e0d6ed",
|
||||
},
|
||||
{
|
||||
name: "Red",
|
||||
color: "#fdebeb",
|
||||
color: "#ffc6c2",
|
||||
},
|
||||
{
|
||||
name: "Yellow",
|
||||
color: "#fbf4a2",
|
||||
color: "#faf594",
|
||||
},
|
||||
{
|
||||
name: "Orange",
|
||||
color: "#faebdd",
|
||||
color: "#f5c8a9",
|
||||
},
|
||||
{
|
||||
name: "Pink",
|
||||
color: "#faf1f5",
|
||||
color: "#f5cfe0",
|
||||
},
|
||||
{
|
||||
name: "Gray",
|
||||
color: "#f1f1ef",
|
||||
color: "#dfdfd7",
|
||||
},
|
||||
{
|
||||
name: "Brown",
|
||||
color: "#d7c4b7",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -112,17 +122,21 @@ export const ColorSelector: FC<ColorSelectorProps> = ({
|
||||
|
||||
const editorState = useEditorState({
|
||||
editor,
|
||||
selector: ctx => {
|
||||
selector: (ctx) => {
|
||||
if (!ctx.editor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const activeColors: Record<string, boolean> = {};
|
||||
TEXT_COLORS.forEach(({ color }) => {
|
||||
activeColors[`text_${color}`] = ctx.editor.isActive("textStyle", { color });
|
||||
activeColors[`text_${color}`] = ctx.editor.isActive("textStyle", {
|
||||
color,
|
||||
});
|
||||
});
|
||||
HIGHLIGHT_COLORS.forEach(({ color }) => {
|
||||
activeColors[`highlight_${color}`] = ctx.editor.isActive("highlight", { color });
|
||||
activeColors[`highlight_${color}`] = ctx.editor.isActive("highlight", {
|
||||
color,
|
||||
});
|
||||
});
|
||||
|
||||
return activeColors;
|
||||
@@ -133,67 +147,152 @@ export const ColorSelector: FC<ColorSelectorProps> = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
const activeColorItem = TEXT_COLORS.find(({ color }) =>
|
||||
editorState[`text_${color}`]
|
||||
const activeColorItem = TEXT_COLORS.find(
|
||||
({ color }) => editorState[`text_${color}`],
|
||||
);
|
||||
|
||||
const activeHighlightItem = HIGHLIGHT_COLORS.find(({ color }) =>
|
||||
editorState[`highlight_${color}`]
|
||||
const activeHighlightItem = HIGHLIGHT_COLORS.find(
|
||||
({ color }) => editorState[`highlight_${color}`],
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover width={200} opened={isOpen} withArrow>
|
||||
<Popover width={220} opened={isOpen} withArrow>
|
||||
<Popover.Target>
|
||||
<Tooltip label={t("Text color")} withArrow>
|
||||
<ActionIcon
|
||||
<Button
|
||||
variant="default"
|
||||
size="lg"
|
||||
radius="0"
|
||||
style={{
|
||||
border: "none",
|
||||
color: activeColorItem?.color,
|
||||
}}
|
||||
rightSection={<IconChevronDown size={16} />}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
data-text-color={activeColorItem?.color || ""}
|
||||
data-highlight-color={activeHighlightItem?.color || ""}
|
||||
className="color-selector-trigger"
|
||||
style={{
|
||||
height: "34px",
|
||||
border: "none",
|
||||
fontWeight: 500,
|
||||
fontSize: rem(16),
|
||||
paddingLeft: rem(8),
|
||||
paddingRight: rem(4),
|
||||
}}
|
||||
>
|
||||
<IconPalette size={16} stroke={2} />
|
||||
</ActionIcon>
|
||||
A
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Popover.Target>
|
||||
|
||||
<Popover.Dropdown>
|
||||
{/* make mah responsive */}
|
||||
<ScrollArea.Autosize type="scroll" mah="400">
|
||||
<Text span c="dimmed" tt="uppercase" inherit>
|
||||
{t("Color")}
|
||||
</Text>
|
||||
<Stack gap="md">
|
||||
<Box>
|
||||
<Text size="sm" fw={600} mb="xs">
|
||||
{t("Text color")}
|
||||
</Text>
|
||||
<SimpleGrid cols={5} spacing="xs">
|
||||
{TEXT_COLORS.map(({ name, color }, index) => (
|
||||
<Tooltip key={index} label={t(name)} withArrow>
|
||||
<Box
|
||||
onClick={() => {
|
||||
if (name === "Default") {
|
||||
editor.commands.unsetColor();
|
||||
} else {
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.setColor(color || "")
|
||||
.run();
|
||||
}
|
||||
setIsOpen(false);
|
||||
}}
|
||||
style={{
|
||||
width: rem(28),
|
||||
height: rem(28),
|
||||
borderRadius: rem(6),
|
||||
border: editorState[`text_${color}`]
|
||||
? "2px solid var(--mantine-color-gray-8)"
|
||||
: "1px solid var(--mantine-color-gray-4)",
|
||||
cursor: "pointer",
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
fontSize: rem(16),
|
||||
fontWeight: 600,
|
||||
color: color || "var(--mantine-color-gray-8)",
|
||||
}}
|
||||
>
|
||||
A
|
||||
</Box>
|
||||
</Tooltip>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
|
||||
<Button.Group orientation="vertical">
|
||||
{TEXT_COLORS.map(({ name, color }, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
variant="default"
|
||||
leftSection={<span style={{ color }}>A</span>}
|
||||
justify="left"
|
||||
fullWidth
|
||||
rightSection={
|
||||
editorState[`text_${color}`] && (
|
||||
<IconCheck style={{ width: rem(16) }} />
|
||||
)
|
||||
}
|
||||
onClick={() => {
|
||||
if (name === "Default") {
|
||||
editor.commands.unsetColor();
|
||||
} else {
|
||||
editor.chain().focus().setColor(color || "").run();
|
||||
}
|
||||
setIsOpen(false);
|
||||
}}
|
||||
style={{ border: "none" }}
|
||||
>
|
||||
{t(name)}
|
||||
</Button>
|
||||
))}
|
||||
</Button.Group>
|
||||
<Box>
|
||||
<Text size="sm" fw={600} mb="xs">
|
||||
{t("Highlight color")}
|
||||
</Text>
|
||||
<SimpleGrid cols={5} spacing="xs">
|
||||
{HIGHLIGHT_COLORS.map(({ name, color }, index) => (
|
||||
<Tooltip key={index} label={t(name)} withArrow>
|
||||
<Box
|
||||
onClick={() => {
|
||||
if (name === "Default") {
|
||||
editor.commands.unsetHighlight();
|
||||
} else {
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.toggleMark("highlight", {
|
||||
color: color || "",
|
||||
colorName: name.toLowerCase() || "",
|
||||
})
|
||||
.run();
|
||||
}
|
||||
setIsOpen(false);
|
||||
}}
|
||||
style={{
|
||||
width: rem(28),
|
||||
height: rem(28),
|
||||
borderRadius: rem(4),
|
||||
backgroundColor: color || "var(--mantine-color-gray-2)",
|
||||
border: "1px solid var(--mantine-color-gray-4)",
|
||||
cursor: "pointer",
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
fontSize: rem(16),
|
||||
fontWeight: 600,
|
||||
color: "var(--mantine-color-gray-8)",
|
||||
}}
|
||||
>
|
||||
{editorState[`highlight_${color}`] ? (
|
||||
<IconCheck
|
||||
size={16}
|
||||
color="var(--mantine-color-green-7)"
|
||||
/>
|
||||
) : (
|
||||
"A"
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
|
||||
<Button
|
||||
variant="default"
|
||||
fullWidth
|
||||
onClick={() => {
|
||||
editor.commands.unsetColor();
|
||||
editor.commands.unsetHighlight();
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
{t("Remove color")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</ScrollArea.Autosize>
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
|
||||
Reference in New Issue
Block a user