[WEB-2494] feat: text color and highlight options for all editors (#5653)
* feat: add text color and highlight options to pages * style: rich text editor floating toolbar * chore: remove unused function * refactor: slash command components * chore: move default text and background options to the top * fix: sections filtering logic
This commit is contained in:
parent
5afc576dec
commit
c3c1ea727d
33 changed files with 1166 additions and 542 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
// editor
|
||||
import { EditorRefApi, ILiteTextEditor, LiteTextEditorWithRef } from "@plane/editor";
|
||||
import { EditorRefApi, ILiteTextEditor, LiteTextEditorWithRef, TNonColorEditorCommands } from "@plane/editor";
|
||||
// types
|
||||
import { IUserLite } from "@plane/types";
|
||||
// components
|
||||
|
|
@ -87,7 +87,9 @@ export const LiteTextEditor = React.forwardRef<EditorRefApi, LiteTextEditorWrapp
|
|||
accessSpecifier={accessSpecifier}
|
||||
executeCommand={(key) => {
|
||||
if (isMutableRefObject<EditorRefApi>(ref)) {
|
||||
ref.current?.executeMenuItemCommand(key);
|
||||
ref.current?.executeMenuItemCommand({
|
||||
itemKey: key as TNonColorEditorCommands,
|
||||
});
|
||||
}
|
||||
}}
|
||||
handleAccessChange={handleAccessChange}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import React, { useEffect, useState, useCallback } from "react";
|
||||
import { Globe2, Lock, LucideIcon } from "lucide-react";
|
||||
// editor
|
||||
import { EditorRefApi, TEditorCommands } from "@plane/editor";
|
||||
import { EditorRefApi, TEditorCommands, TNonColorEditorCommands } from "@plane/editor";
|
||||
// ui
|
||||
import { Button, Tooltip } from "@plane/ui";
|
||||
// constants
|
||||
|
|
@ -69,7 +69,9 @@ export const IssueCommentToolbar: React.FC<Props> = (props) => {
|
|||
.flat()
|
||||
.forEach((item) => {
|
||||
// Assert that editorRef.current is not null
|
||||
newActiveStates[item.key] = (editorRef.current as EditorRefApi).isMenuItemActive(item.key);
|
||||
newActiveStates[item.key] = (editorRef.current as EditorRefApi).isMenuItemActive({
|
||||
itemKey: item.key as TNonColorEditorCommands,
|
||||
});
|
||||
});
|
||||
setActiveStates(newActiveStates);
|
||||
}
|
||||
|
|
|
|||
127
web/core/components/pages/editor/header/color-dropdown.tsx
Normal file
127
web/core/components/pages/editor/header/color-dropdown.tsx
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
import { Popover } from "@headlessui/react";
|
||||
import { ALargeSmall, Ban } from "lucide-react";
|
||||
// plane editor
|
||||
import { COLORS_LIST, TColorEditorCommands } from "@plane/editor";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
|
||||
type Props = {
|
||||
handleColorSelect: (key: TColorEditorCommands, color: string | undefined) => void;
|
||||
isColorActive: (key: TColorEditorCommands, color: string | undefined) => boolean;
|
||||
};
|
||||
|
||||
export const ColorDropdown: React.FC<Props> = memo((props) => {
|
||||
const { handleColorSelect, isColorActive } = props;
|
||||
|
||||
const activeTextColor = COLORS_LIST.find((c) => isColorActive("text-color", c.textColor));
|
||||
const activeBackgroundColor = COLORS_LIST.find((c) => isColorActive("background-color", c.backgroundColor));
|
||||
|
||||
return (
|
||||
<Popover as="div" className="h-7 px-2">
|
||||
<Popover.Button
|
||||
as="button"
|
||||
type="button"
|
||||
className={({ open }) =>
|
||||
cn("h-full", {
|
||||
"outline-none": open,
|
||||
})
|
||||
}
|
||||
>
|
||||
{({ open }) => (
|
||||
<span
|
||||
className={cn(
|
||||
"h-full px-2 text-custom-text-300 text-sm flex items-center gap-1.5 rounded hover:bg-custom-background-80",
|
||||
{
|
||||
"text-custom-text-100 bg-custom-background-80": open,
|
||||
}
|
||||
)}
|
||||
>
|
||||
Color
|
||||
<span
|
||||
className={cn(
|
||||
"flex-shrink-0 size-6 grid place-items-center rounded border-[0.5px] border-custom-border-300",
|
||||
{
|
||||
"bg-custom-background-100": !activeBackgroundColor,
|
||||
}
|
||||
)}
|
||||
style={
|
||||
activeBackgroundColor
|
||||
? {
|
||||
backgroundColor: activeBackgroundColor.backgroundColor,
|
||||
}
|
||||
: {}
|
||||
}
|
||||
>
|
||||
<ALargeSmall
|
||||
className={cn("size-3.5", {
|
||||
"text-custom-text-100": !activeTextColor,
|
||||
})}
|
||||
style={
|
||||
activeTextColor
|
||||
? {
|
||||
color: activeTextColor.textColor,
|
||||
}
|
||||
: {}
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
</Popover.Button>
|
||||
<Popover.Panel
|
||||
as="div"
|
||||
className="fixed z-20 mt-1 rounded-md border-[0.5px] border-custom-border-300 bg-custom-background-100 shadow-custom-shadow-rg p-2 space-y-2"
|
||||
>
|
||||
<div className="space-y-1.5">
|
||||
<p className="text-xs text-custom-text-300 font-semibold">Text colors</p>
|
||||
<div className="flex items-center gap-2">
|
||||
{COLORS_LIST.map((color) => (
|
||||
<button
|
||||
key={color.textColor}
|
||||
type="button"
|
||||
className="flex-shrink-0 size-6 rounded border-[0.5px] border-custom-border-400 hover:opacity-60 transition-opacity"
|
||||
style={{
|
||||
backgroundColor: color.textColor,
|
||||
}}
|
||||
onClick={() => handleColorSelect("text-color", color.textColor)}
|
||||
/>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
className="flex-shrink-0 size-6 grid place-items-center rounded text-custom-text-300 border-[0.5px] border-custom-border-400 hover:bg-custom-background-80 transition-colors"
|
||||
onClick={() => handleColorSelect("text-color", undefined)}
|
||||
>
|
||||
<Ban className="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<p className="text-xs text-custom-text-300 font-semibold">Background colors</p>
|
||||
<div className="flex items-center gap-2">
|
||||
{COLORS_LIST.map((color) => (
|
||||
<button
|
||||
key={color.backgroundColor}
|
||||
type="button"
|
||||
className="flex-shrink-0 size-6 rounded border-[0.5px] border-custom-border-400 hover:opacity-60 transition-opacity"
|
||||
style={{
|
||||
backgroundColor: color.backgroundColor,
|
||||
}}
|
||||
onClick={() => handleColorSelect("background-color", color.backgroundColor)}
|
||||
/>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
className="flex-shrink-0 size-6 grid place-items-center rounded text-custom-text-300 border-[0.5px] border-custom-border-400 hover:bg-custom-background-80 transition-colors"
|
||||
onClick={() => handleColorSelect("background-color", undefined)}
|
||||
>
|
||||
<Ban className="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Popover>
|
||||
);
|
||||
});
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
export * from "./color-dropdown";
|
||||
export * from "./extra-options";
|
||||
export * from "./info-popover";
|
||||
export * from "./options-dropdown";
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@
|
|||
import React, { useEffect, useState, useCallback } from "react";
|
||||
import { Check, ChevronDown } from "lucide-react";
|
||||
// editor
|
||||
import { EditorRefApi, TEditorCommands } from "@plane/editor";
|
||||
import { EditorRefApi, TNonColorEditorCommands } from "@plane/editor";
|
||||
// ui
|
||||
import { CustomMenu, Tooltip } from "@plane/ui";
|
||||
// components
|
||||
import { ColorDropdown } from "@/components/pages";
|
||||
// constants
|
||||
import { TOOLBAR_ITEMS, TYPOGRAPHY_ITEMS, ToolbarMenuItem } from "@/constants/editor";
|
||||
// helpers
|
||||
|
|
@ -18,7 +20,7 @@ type Props = {
|
|||
type ToolbarButtonProps = {
|
||||
item: ToolbarMenuItem;
|
||||
isActive: boolean;
|
||||
executeCommand: (commandKey: TEditorCommands) => void;
|
||||
executeCommand: EditorRefApi["executeMenuItemCommand"];
|
||||
};
|
||||
|
||||
const ToolbarButton: React.FC<ToolbarButtonProps> = React.memo((props) => {
|
||||
|
|
@ -36,7 +38,11 @@ const ToolbarButton: React.FC<ToolbarButtonProps> = React.memo((props) => {
|
|||
<button
|
||||
key={item.key}
|
||||
type="button"
|
||||
onClick={() => executeCommand(item.key)}
|
||||
onClick={() =>
|
||||
executeCommand({
|
||||
itemKey: item.key as TNonColorEditorCommands,
|
||||
})
|
||||
}
|
||||
className={cn("grid size-7 place-items-center rounded text-custom-text-300 hover:bg-custom-background-80", {
|
||||
"bg-custom-background-80 text-custom-text-100": isActive,
|
||||
})}
|
||||
|
|
@ -56,6 +62,7 @@ ToolbarButton.displayName = "ToolbarButton";
|
|||
const toolbarItems = TOOLBAR_ITEMS.document;
|
||||
|
||||
export const PageToolbar: React.FC<Props> = ({ editorRef }) => {
|
||||
// states
|
||||
const [activeStates, setActiveStates] = useState<Record<string, boolean>>({});
|
||||
|
||||
const updateActiveStates = useCallback(() => {
|
||||
|
|
@ -63,7 +70,9 @@ export const PageToolbar: React.FC<Props> = ({ editorRef }) => {
|
|||
Object.values(toolbarItems)
|
||||
.flat()
|
||||
.forEach((item) => {
|
||||
newActiveStates[item.key] = editorRef.isMenuItemActive(item.key);
|
||||
newActiveStates[item.key] = editorRef.isMenuItemActive({
|
||||
itemKey: item.key as TNonColorEditorCommands,
|
||||
});
|
||||
});
|
||||
setActiveStates(newActiveStates);
|
||||
}, [editorRef]);
|
||||
|
|
@ -74,7 +83,11 @@ export const PageToolbar: React.FC<Props> = ({ editorRef }) => {
|
|||
return () => unsubscribe();
|
||||
}, [editorRef, updateActiveStates]);
|
||||
|
||||
const activeTypography = TYPOGRAPHY_ITEMS.find((item) => editorRef.isMenuItemActive(item.key));
|
||||
const activeTypography = TYPOGRAPHY_ITEMS.find((item) =>
|
||||
editorRef.isMenuItemActive({
|
||||
itemKey: item.key as TNonColorEditorCommands,
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center divide-x divide-custom-border-200">
|
||||
|
|
@ -94,7 +107,11 @@ export const PageToolbar: React.FC<Props> = ({ editorRef }) => {
|
|||
<CustomMenu.MenuItem
|
||||
key={item.key}
|
||||
className="flex items-center justify-between gap-2"
|
||||
onClick={() => editorRef.executeMenuItemCommand(item.key)}
|
||||
onClick={() =>
|
||||
editorRef.executeMenuItemCommand({
|
||||
itemKey: item.key as TNonColorEditorCommands,
|
||||
})
|
||||
}
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<item.icon className="size-3" />
|
||||
|
|
@ -104,6 +121,20 @@ export const PageToolbar: React.FC<Props> = ({ editorRef }) => {
|
|||
</CustomMenu.MenuItem>
|
||||
))}
|
||||
</CustomMenu>
|
||||
<ColorDropdown
|
||||
handleColorSelect={(key, color) =>
|
||||
editorRef.executeMenuItemCommand({
|
||||
itemKey: key,
|
||||
color,
|
||||
})
|
||||
}
|
||||
isColorActive={(key, color) =>
|
||||
editorRef.isMenuItemActive({
|
||||
itemKey: key,
|
||||
color,
|
||||
})
|
||||
}
|
||||
/>
|
||||
{Object.keys(toolbarItems).map((key) => (
|
||||
<div key={key} className="flex items-center gap-0.5 px-2 first:pl-0 last:pr-0">
|
||||
{toolbarItems[key].map((item) => (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue