[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:
Aaryan Khandelwal 2024-10-08 18:42:47 +05:30 committed by GitHub
parent 5afc576dec
commit c3c1ea727d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 1166 additions and 542 deletions

View file

@ -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}

View file

@ -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);
}

View 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>
);
});

View file

@ -1,3 +1,4 @@
export * from "./color-dropdown";
export * from "./extra-options";
export * from "./info-popover";
export * from "./options-dropdown";

View file

@ -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) => (