feat: revamp color picker (#23)

* chore: import new color json

* chore: generate css vars

* chore: render text and bg colors using tokens

* feat: implement new color picker

* fix: transfer popover focus

* fix: replicate logic onto background popover

* chore: adjust i18n

* chore: fix selection loss on interaction with popover

* chore: pass getSubscriptions in EditorContext

* chore: default color tooltip

* fix: forward ref in color tile

* fix: editor focus after opening popover

* chore: fix lint

* chore: maintain selection after changing color

* fix: font color for href and inline code

* fix: focus and popover conflicts

* chore: revert unnecessary forceShow

* chore: dummy commit

* fix: don't break cover
This commit is contained in:
Richard Shiue
2025-07-31 15:06:47 +08:00
committed by GitHub
parent ee5124b679
commit 4861ece3ca
27 changed files with 1986 additions and 348 deletions

View File

@@ -433,6 +433,7 @@
"confirm": "Confirm",
"done": "Done",
"cancel": "Cancel",
"apply": "Apply",
"signIn": "Sign In",
"signOut": "Sign Out",
"complete": "Complete",
@@ -2252,7 +2253,8 @@
"deleteContentCaption": "if you delete this {{pageType}}, you can restore it from the trash."
},
"colors": {
"custom": "Custom",
"recent": "Recent color",
"custom": "Custom color",
"default": "Default",
"red": "Red",
"orange": "Orange",
@@ -2262,7 +2264,27 @@
"purple": "Purple",
"pink": "Pink",
"brown": "Brown",
"gray": "Gray"
"gray": "Gray",
"rose": "Rose",
"papaya": "Papaya",
"tangerine": "Tangerine",
"mango": "Mango",
"lemon": "Lemon",
"olive": "Olive",
"lime": "Lime",
"grass": "Grass",
"forest": "Forest",
"jade": "Jade",
"aqua": "Aqua",
"azure": "Azure",
"denim": "Denim",
"mauve": "Mauve",
"lavender": "Lavender",
"lilac": "Lilac",
"mallow": "Mallow",
"camellia": "Camellia",
"smoke": "Smoke",
"iron": "Iron"
},
"emoji": {
"emojiTab": "Emoji",

View File

@@ -30,7 +30,8 @@ export enum EditorMarkFormat {
Formula = 'formula',
Mention = 'mention',
FontColor = 'font_color',
FontToken = 'af_text_color',
BgColor = 'bg_color',
BgToken = 'af_background_color',
Align = 'align',
}

View File

@@ -1,4 +1,4 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.55279 12.7764C4.42929 13.0234 4.5294 13.3237 4.77639 13.4472C5.02338 13.5707 5.32372 13.4706 5.44721 13.2236L4.55279 12.7764ZM10 3L10.4472 2.77639C10.3625 2.607 10.1894 2.5 10 2.5C9.81061 2.5 9.63748 2.607 9.55279 2.77639L10 3ZM14.5528 13.2236C14.6763 13.4706 14.9766 13.5707 15.2236 13.4472C15.4706 13.3237 15.5707 13.0234 15.4472 12.7764L14.5528 13.2236ZM5.44721 13.2236L7.11388 9.89027L6.21945 9.44306L4.55279 12.7764L5.44721 13.2236ZM7.11388 9.89027L10.4472 3.22361L9.55279 2.77639L6.21945 9.44306L7.11388 9.89027ZM9.55279 3.22361L12.8861 9.89027L13.7805 9.44306L10.4472 2.77639L9.55279 3.22361ZM12.8861 9.89027L14.5528 13.2236L15.4472 12.7764L13.7805 9.44306L12.8861 9.89027ZM6.66667 10.1667H13.3333V9.16667H6.66667V10.1667Z" fill="currentColor"/>
<path d="M2 17H18" stroke="currentColor" stroke-width="2.5"/>
<path d="M3.17158 3.17158C2 4.34314 2 6.22876 2 10C2 13.7712 2 15.6569 3.17158 16.8284C4.34314 18 6.22875 18 9.99998 18H10H10C13.7712 18 15.6569 18 16.8284 16.8284C18 15.6569 18 13.7712 18 10V10V9.99998C18 6.22875 18 4.34314 16.8284 3.17158C15.6569 2 13.7712 2 10 2C6.22876 2 4.34314 2 3.17158 3.17158Z" stroke="currentColor" stroke-linecap="round"/>
<path d="M6.5 13.5L7.66667 11.1667M7.66667 11.1667L10 6.5L12.3333 11.1667M7.66667 11.1667H12.3333M12.3333 11.1667L13.5 13.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 930 B

After

Width:  |  Height:  |  Size: 650 B

View File

@@ -1,4 +1,4 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 4.66667C10.2498 6.36375 11.5514 7.66117 13.1667 7.83333M10.7911 3.86693L5.65847 9.10195C5.46467 9.30075 5.27712 9.69232 5.23961 9.96341L5.0083 11.9152C4.92702 12.6201 5.45217 13.102 6.17736 12.9815L8.19041 12.6502C8.47174 12.602 8.8656 12.4032 9.0594 12.1984L14.192 6.96336C15.0798 6.05974 15.4799 5.0296 14.0983 3.77054C12.7229 2.52354 11.6789 2.9633 10.7911 3.86693Z" stroke="currentColor" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2 17H18" stroke="currentColor" stroke-width="2.5" stroke-miterlimit="10" stroke-linejoin="round"/>
<path d="M2 10C2 6.22876 2 4.34314 3.17158 3.17158C4.34314 2 6.22876 2 10 2C13.7712 2 15.6569 2 16.8284 3.17158C18 4.34314 18 6.22876 18 10C18 13.7712 18 15.6569 16.8284 16.8284C15.6569 18 13.7712 18 10 18C6.22876 18 4.34314 18 3.17158 16.8284C2 15.6569 2 13.7712 2 10Z" stroke="currentColor" stroke-linecap="round"/>
<path d="M10 7.33333C10.1999 8.691 11.2411 9.72893 12.5333 9.86667M10.6329 6.69355L6.52678 10.8816C6.37174 11.0406 6.2217 11.3539 6.19169 11.5707L6.00664 13.1322C5.94162 13.6961 6.36173 14.0816 6.94189 13.9852L8.55233 13.7202C8.77739 13.6816 9.09248 13.5226 9.24752 13.3587L13.3536 9.17069C14.0638 8.44779 14.3839 7.62368 13.2786 6.61644C12.1783 5.61883 11.3431 5.97064 10.6329 6.69355Z" stroke="currentColor" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 689 B

After

Width:  |  Height:  |  Size: 903 B

View File

@@ -0,0 +1,46 @@
import { forwardRef, useMemo } from 'react';
import { cn } from '@/lib/utils';
export const ColorTile = forwardRef<
HTMLDivElement,
{
value: string;
isText?: boolean;
onClick: () => void;
active?: boolean;
}
>(({ value, onClick, isText = false, active = false, ...props }, forwardedRef) => {
const child = useMemo(() => {
if (isText) {
return (
<span className='' style={{ color: value }}>
A
</span>
);
}
return (
<div
className={cn(active ? 'h-5 w-5 rounded-[3px]' : 'min-h-6 min-w-6 rounded-[4px]')}
style={{ backgroundColor: value }}
/>
);
}, [active, isText, value]);
return (
<div
ref={forwardedRef}
{...props}
onClick={onClick}
className={cn(
'flex h-7 w-7 cursor-pointer items-center justify-center rounded-[6px]',
active
? 'border-[2px] border-fill-theme-thick'
: 'border border-border-primary hover:border-border-primary-hover'
)}
>
{child}
</div>
);
});

View File

@@ -0,0 +1,132 @@
import { useCallback, useState } from 'react';
import { HexColorPicker } from 'react-colorful';
import { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import './style.css';
export function CustomColorPicker({
value,
transparency = '',
onCancel,
onApply,
}: {
value: string;
transparency?: string;
onCancel: () => void;
onApply: (color: string) => void;
}) {
const { t } = useTranslation();
const [color, setColor] = useState(argbToHtmlHex(value));
const [colorHex, setColorHex] = useState(argbToHtmlHex(value));
const handleChange = useCallback((newColor: string) => {
if (!newColor) {
return;
}
const color = argbToHtmlHex(newColor);
setColor(color);
setColorHex(color);
}, []);
return (
<div className='flex flex-col gap-3'>
<HexColorPicker color={color} onChange={handleChange} className='custom-color-picker' />
<div className='mb-5 flex gap-2'>
<div style={{ backgroundColor: color }} className='h-8 w-8 rounded-[16px] border border-border-primary' />
<Input
value={colorHex}
onChange={(e) => setColorHex(e.target.value)}
onBlur={(e) => handleChange(e.currentTarget.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
(e.target as HTMLInputElement).blur();
}
}}
/>
</div>
<div className='flex justify-end gap-2'>
<Button variant='outline' onClick={onCancel}>
{t('button.cancel')}
</Button>
<Button
onClick={() => {
const converted = rgbaToArgb(color + transparency);
onApply(converted);
}}
>
{t('button.apply')}
</Button>
</div>
</div>
);
}
function rgbaToArgb(color: string): string {
if (!color) {
return '';
}
const hexRegex = /^#([A-Fa-f0-9]{6})([A-Fa-f0-9]{2})?$/;
const match = color.match(hexRegex);
if (!match) {
return '';
}
const r = parseInt(match[1].substring(0, 2), 16);
const g = parseInt(match[1].substring(2, 4), 16);
const b = parseInt(match[1].substring(4, 6), 16);
let a = 255;
if (typeof match[2] !== 'undefined') {
a = parseInt(match[2], 16);
}
const hex = (v: number) => Math.max(0, Math.min(255, v)).toString(16).padStart(2, '0');
return `0x${hex(a)}${hex(r)}${hex(g)}${hex(b)}`;
}
function argbToHtmlHex(color: string): string {
if (!color) {
return '#ffffff';
}
const rgbaRegex = /^#([A-Fa-f0-9]{6})([A-Fa-f0-9]{2})?$/;
const rgbaMatch = color.match(rgbaRegex);
if (rgbaMatch) {
return `#${rgbaMatch[1]}`;
}
const hexRegex = /^0x([A-Fa-f0-9]{8}|[A-Fa-f0-9]{6})$/;
const match = color.match(hexRegex);
if (!match) {
return '#ffffff';
}
let r = 0,
g = 0,
b = 0;
if (match[1].length === 8) {
r = parseInt(match[1].substring(2, 4), 16);
g = parseInt(match[1].substring(4, 6), 16);
b = parseInt(match[1].substring(6, 8), 16);
} else if (match[1].length === 6) {
r = parseInt(match[1].substring(0, 2), 16);
g = parseInt(match[1].substring(2, 4), 16);
b = parseInt(match[1].substring(4, 6), 16);
}
const hex = (v: number) => Math.max(0, Math.min(255, v)).toString(16).padStart(2, '0');
return `#${hex(r)}${hex(g)}${hex(b)}`;
}

View File

@@ -1 +1,2 @@
export * from './ColorPicker';
export * from './ColorPicker';
export * from './ColorTile';

View File

@@ -0,0 +1,36 @@
.custom-color-picker.react-colorful {
width: auto;
}
.custom-color-picker .react-colorful__saturation {
height: 160px;
margin-bottom: 12px;
border-radius: 8px;
border: 1px solid var(--border-primary);
box-shadow: none;
overflow: hidden;
}
.custom-color-picker .react-colorful__hue {
height: 24px;
border-radius: 6px;
overflow: hidden;
}
.custom-color-picker .react-colorful__saturation-pointer,
.custom-color-picker .react-colorful__hue-pointer {
width: 23px;
height: 23px;
border-radius: 12px;
border: 2px solid white;
outline: 1px solid black;
box-shadow: none;
}
.custom-color-picker .react-colorful__pointer-fill {
background-color: transparent;
}
.custom-color-picker .react-colorful__interactive:focus .react-colorful__pointer {
transform: translate(-50%, -50%);
}

View File

@@ -10,6 +10,7 @@ import {
TextCount,
LoadDatabasePrompts,
TestDatabasePromptConfig,
Subscription,
MentionablePerson,
} from '@/application/types';
import { AxiosInstance } from 'axios';
@@ -66,6 +67,7 @@ export interface EditorContextState {
getMoreAIContext?: () => string;
loadDatabasePrompts?: LoadDatabasePrompts;
testDatabasePromptConfig?: TestDatabasePromptConfig;
getSubscriptions?: (() => Promise<Subscription[]>) | undefined;
eventEmitter?: EventEmitter;
getMentionUser?: (uuid: string) => Promise<MentionablePerson | undefined>;
}

View File

@@ -1,21 +1,19 @@
import { CSSProperties } from 'react';
import { RenderLeafProps } from 'slate-react';
import { Mention } from '@/application/types';
import FormulaLeaf from '@/components/editor/components/leaf/formula/FormulaLeaf';
import { Href } from '@/components/editor/components/leaf/href';
import MentionLeaf from '@/components/editor/components/leaf/mention/MentionLeaf';
import { cn } from '@/lib/utils';
import { renderColor } from '@/utils/color';
import { getFontFamily } from '@/utils/font';
import { CSSProperties } from 'react';
import { RenderLeafProps } from 'slate-react';
export function Leaf({ attributes, children, leaf, text }: RenderLeafProps) {
let newChildren = children;
const classList = [leaf.prism_token, leaf.prism_token && 'token', leaf.class_name].filter(Boolean);
if (leaf.code && !(leaf.formula || leaf.mention)) {
newChildren = <span className={'bg-border-primary font-medium text-[#EB5757]'}>{newChildren}</span>;
}
if (leaf.underline) {
newChildren = <u>{newChildren}</u>;
}
@@ -44,9 +42,33 @@ export function Leaf({ attributes, children, leaf, text }: RenderLeafProps) {
style['backgroundColor'] = renderColor(leaf.bg_color);
}
if (leaf.af_text_color) {
if (!classList.includes('text-color')) {
classList.push('text-color');
}
style['color'] = renderColor(leaf.af_text_color);
}
if (leaf.af_background_color) {
if (!classList.includes('bg-color')) {
classList.push('bg-color');
}
style['backgroundColor'] = renderColor(leaf.af_background_color);
}
if (leaf.code && !(leaf.formula || leaf.mention)) {
newChildren = (
<span className={cn('bg-border-primary font-medium', style['color'] ? undefined : 'text-[#EB5757]')}>
{newChildren}
</span>
);
}
if (leaf.href) {
newChildren = (
<Href text={text} leaf={leaf}>
<Href text={text} leaf={leaf} textColor={style['color']}>
{newChildren}
</Href>
);

View File

@@ -13,7 +13,7 @@ import { useTranslation } from 'react-i18next';
import { Element, Text } from 'slate';
import { ReactEditor, useReadOnly, useSlateStatic } from 'slate-react';
export const Href = memo(({ text, children, leaf }: { leaf: Text; children: React.ReactNode; text: Text }) => {
export const Href = memo(({ text, children, leaf, textColor }: { leaf: Text; children: React.ReactNode; text: Text; textColor?: string }) => {
const editor = useSlateStatic() as YjsEditor;
const readOnly = useReadOnly() || editor.isElementReadOnly(text as unknown as Element);
@@ -62,8 +62,9 @@ export const Href = memo(({ text, children, leaf }: { leaf: Text; children: Reac
}}
style={{
backgroundColor: selected ? 'var(--content-blue-100)' : undefined,
color: textColor || 'var(--text-action)',
}}
className={`cursor-pointer select-auto py-0.5 text-text-action underline`}
className={`cursor-pointer select-auto py-0.5 underline`}
>
{children}
{hovered && (

View File

@@ -66,9 +66,7 @@ export function useVisible() {
}, [visible, removeDecorate]);
useEffect(() => {
const handleMouseDown = () => {
const { selection } = editor;
if(selection && Range.isExpanded(selection)) {

View File

@@ -6,7 +6,7 @@ import Align from '@/components/editor/components/toolbar/selection-toolbar/acti
import Bold from '@/components/editor/components/toolbar/selection-toolbar/actions/Bold';
import BulletedList from '@/components/editor/components/toolbar/selection-toolbar/actions/BulletedList';
import Href from '@/components/editor/components/toolbar/selection-toolbar/actions/Href';
import Color from '@/components/editor/components/toolbar/selection-toolbar/actions/Color';
import TextColor from '@/components/editor/components/toolbar/selection-toolbar/actions/TextColor';
import Formula from '@/components/editor/components/toolbar/selection-toolbar/actions/Formula';
import Heading from '@/components/editor/components/toolbar/selection-toolbar/actions/Heading';
import InlineCode from '@/components/editor/components/toolbar/selection-toolbar/actions/InlineCode';
@@ -21,15 +21,65 @@ import {
import { Divider } from '@mui/material';
import { Editor, Element, Path } from 'slate';
import Paragraph from './actions/Paragraph';
import { useMemo } from 'react';
import { useSlate } from 'slate-react';
import { useCallback, useMemo, useRef } from 'react';
import { ReactEditor, useSlate } from 'slate-react';
import BgColor from './actions/BgColor';
import { useEditorContext } from '@/components/editor/EditorContext';
function ToolbarActions() {
const editor = useSlate() as YjsEditor;
const selection = editor.selection;
const {
forceShow,
visible: toolbarVisible,
} = useSelectionToolbarContext();
const { removeDecorate } = useEditorContext();
const refocusTimeout = useRef<NodeJS.Timeout | null>(null);
const disableFocusRef = useRef<boolean>(false);
const focusEditor = useCallback(
(debounce: number | undefined) => {
if (disableFocusRef.current) {
return;
}
if (refocusTimeout.current) {
clearTimeout(refocusTimeout.current);
}
if (debounce === undefined) {
ReactEditor.focus(editor);
forceShow(false);
removeDecorate?.('selection-toolbar');
return;
}
refocusTimeout.current = setTimeout(() => {
if (!disableFocusRef.current) {
ReactEditor.focus(editor);
forceShow(false);
removeDecorate?.('selection-toolbar');
}
}, debounce);
},
[editor, forceShow, removeDecorate]
);
const toggleDisableEditorFocus = useCallback(() => {
if (refocusTimeout.current) {
clearTimeout(refocusTimeout.current);
refocusTimeout.current = null;
}
if (disableFocusRef.current) {
setTimeout(() => {
disableFocusRef.current = false;
}, 50);
} else {
disableFocusRef.current = true;
}
}, []);
const start = useMemo(() => selection ? editor.start(selection) : null, [editor, selection]);
const end = useMemo(() => selection ? editor.end(selection) : null, [editor, selection]);
@@ -111,7 +161,8 @@ function ToolbarActions() {
</>
}
{!isCodeBlock && <Align enabled={toolbarVisible} />}
<Color />
<TextColor focusEditor={focusEditor} toggleDisableEditorFocus={toggleDisableEditorFocus} />
<BgColor focusEditor={focusEditor} toggleDisableEditorFocus={toggleDisableEditorFocus} />
</div>
);
}

View File

@@ -0,0 +1,414 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSlateStatic } from 'slate-react';
import { YjsEditor } from '@/application/slate-yjs';
import { CustomEditor } from '@/application/slate-yjs/command';
import { EditorMarkFormat } from '@/application/slate-yjs/types';
import { SubscriptionPlan } from '@/application/types';
import { ReactComponent as ColorSvg } from '@/assets/icons/text_highlight.svg';
import { ColorTile } from '@/components/_shared/color-picker';
import { CustomColorPicker } from '@/components/_shared/color-picker/CustomColorPicker';
import { useSelectionToolbarContext } from '@/components/editor/components/toolbar/selection-toolbar/SelectionToolbar.hooks';
import { useEditorContext } from '@/components/editor/EditorContext';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { Separator } from '@/components/ui/separator';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { renderColor } from '@/utils/color';
import ActionButton from './ActionButton';
import { CreateCustomColorTile } from './TextColor';
function BgColor({
focusEditor: focusEditor,
toggleDisableEditorFocus,
}: {
focusEditor: (timeout?: number) => void;
toggleDisableEditorFocus: () => void;
}) {
const { t } = useTranslation();
const { getSubscriptions } = useEditorContext();
const { visible, forceShow } = useSelectionToolbarContext();
const editor = useSlateStatic() as YjsEditor;
const marks = CustomEditor.getAllMarks(editor).map(
(mark) => mark[EditorMarkFormat.BgToken] || mark[EditorMarkFormat.BgColor] || ''
);
const unique = [...new Set(marks)];
const singleColor = unique.length === 1 ? unique[0] : undefined;
const [isOpen, setIsOpen] = useState(false);
const [isChoosingCustom, setIsChoosingCustom] = useState(false);
const [recentColors, setRecentColors] = useState<string[]>([]);
const [customColors, setCustomColors] = useState<string[]>([]);
const recentColorToSave = useRef<string | null>(null);
const initialColor = useRef<string | null>(null);
const [activeSubscriptionPlan, setActiveSubscriptionPlan] = useState<SubscriptionPlan | null>(null);
const isPro = activeSubscriptionPlan === SubscriptionPlan.Pro;
const loadSubscription = useCallback(async () => {
try {
const subscriptions = await getSubscriptions?.();
if (!subscriptions || subscriptions.length === 0) {
setActiveSubscriptionPlan(SubscriptionPlan.Free);
return;
}
const subscription = subscriptions[0];
setActiveSubscriptionPlan(subscription?.plan || SubscriptionPlan.Free);
} catch (e) {
setActiveSubscriptionPlan(SubscriptionPlan.Free);
console.error(e);
}
}, [getSubscriptions]);
const isCustomColor = useCallback((color: string) => {
return color.startsWith('#') || color.startsWith('0x');
}, []);
const loadRecentAndCustomColors = useCallback(() => {
const recent = localStorage.getItem('recent-bg-colors');
const custom = localStorage.getItem('custom-bg-colors');
try {
const recentParsed: string[] = recent ? JSON.parse(recent) : [];
setRecentColors(recentParsed.slice(0, 6));
} catch (e) {
console.error('Failed to parse recent colors:', e);
}
try {
const customParsed: string[] = custom ? JSON.parse(custom) : [];
let updatedCustomColors = customParsed.slice(0, 5);
if (singleColor !== undefined && isCustomColor(singleColor) && !updatedCustomColors.includes(singleColor)) {
updatedCustomColors = [singleColor, ...updatedCustomColors].slice(0, 5);
}
setCustomColors(updatedCustomColors);
} catch (e) {
console.error('Failed to parse recent colors:', e);
}
}, [isCustomColor, singleColor]);
useEffect(() => {
if (!visible && isOpen) {
setIsOpen(false);
}
}, [isOpen, visible]);
useEffect(() => {
void loadSubscription();
}, [loadSubscription]);
const getRawColorValue = useCallback(
(color: string) => {
if (isCustomColor(color)) {
return color;
}
const computedStyle = window.getComputedStyle(document.documentElement);
if (color === '' || color === 'bg-default') {
return '#ffffff';
}
return computedStyle.getPropertyValue(`--palette-${color}`);
},
[isCustomColor]
);
const handlePickColor = useCallback(
(color: string) => {
if (color === '' || color === 'bg-default' || color === singleColor) {
CustomEditor.removeMark(editor, EditorMarkFormat.BgColor);
CustomEditor.removeMark(editor, EditorMarkFormat.BgToken);
forceShow(true);
return;
}
if (isCustomColor(color)) {
CustomEditor.addMark(editor, {
key: EditorMarkFormat.BgColor,
value: color,
});
CustomEditor.removeMark(editor, EditorMarkFormat.BgToken);
} else {
CustomEditor.addMark(editor, {
key: EditorMarkFormat.BgColor,
value: window.getComputedStyle(document.documentElement).getPropertyValue(`--palette-${color}`),
});
CustomEditor.addMark(editor, {
key: EditorMarkFormat.BgToken,
value: color,
});
}
forceShow(true);
recentColorToSave.current = color;
},
[editor, forceShow, isCustomColor, singleColor]
);
const handleCreateCustomColor = useCallback(
(color: string) => {
if (!color || customColors.includes(color)) {
return;
}
const updatedCustomColors = [...customColors, color].slice(-5);
setCustomColors(updatedCustomColors);
localStorage.setItem('custom-bg-colors', JSON.stringify(updatedCustomColors));
},
[customColors]
);
const saveRecentColors = useCallback(() => {
if (!recentColorToSave.current) {
return;
}
const color = recentColorToSave.current;
if (color && color !== initialColor.current) {
const updated = [color, ...recentColors.filter((c) => c !== color)].slice(0, 6);
localStorage.setItem('recent-bg-colors', JSON.stringify(updated));
}
recentColorToSave.current = null;
}, [recentColors]);
const builtinColors = useMemo(() => {
return isPro
? [
{
label: t('colors.default'),
color: '',
},
{
label: t('colors.smoke'),
color: 'bg-color-19',
},
{
label: t('colors.mallow'),
color: 'bg-color-17',
},
{
label: t('colors.lavender'),
color: 'bg-color-15',
},
{
label: t('colors.denim'),
color: 'bg-color-13',
},
{
label: t('colors.aqua'),
color: 'bg-color-11',
},
{
label: t('colors.forest'),
color: 'bg-color-9',
},
{
label: t('colors.lime'),
color: 'bg-color-7',
},
{
label: t('colors.lemon'),
color: 'bg-color-5',
},
{
label: t('colors.tangerine'),
color: 'bg-color-3',
},
{
label: t('colors.rose'),
color: 'bg-color-1',
},
]
: [
{
label: t('colors.default'),
color: '',
},
{
label: t('colors.smoke'),
color: 'bg-color-19',
},
{
label: t('colors.mallow'),
color: 'bg-color-17',
},
{
label: t('colors.lavender'),
color: 'bg-color-15',
},
{
label: t('colors.aqua'),
color: 'bg-color-11',
},
{
label: t('colors.lime'),
color: 'bg-color-7',
},
{
label: t('colors.lemon'),
color: 'bg-color-5',
},
{
label: t('colors.tangerine'),
color: 'bg-color-3',
},
];
}, [isPro, t]);
const handleOpen = useCallback(() => {
setIsChoosingCustom(false);
initialColor.current = singleColor || null;
loadRecentAndCustomColors();
}, [loadRecentAndCustomColors, singleColor]);
const popoverContent = useMemo(() => {
if (isChoosingCustom) {
return (
<div className='w-[236px] p-3'>
<CustomColorPicker
value={getRawColorValue(singleColor ?? '#ffffff')}
transparency='99'
onCancel={() => setIsChoosingCustom(false)}
onApply={(color) => {
handleCreateCustomColor(color);
handlePickColor(color);
setIsChoosingCustom(false);
}}
/>
</div>
);
}
return (
<div className={'flex w-[236px] flex-col py-1.5'}>
{recentColors.length > 0 && (
<>
<div className={'px-3.5 pb-2 pt-1.5 text-xs font-medium text-text-tertiary'}>{t('colors.recent')}</div>
<div className={'flex gap-2 px-3.5 pb-1.5'}>
{recentColors.map((color, index) => (
<ColorTile
key={index}
value={renderColor(color)}
active={singleColor === color}
onClick={() => handlePickColor(color)}
/>
))}
</div>
<Separator className={'my-2'} />
</>
)}
<div className={'px-3.5 pb-2 pt-1.5 text-xs font-medium text-text-tertiary'}>{t('editor.backgroundColor')}</div>
<div className={'flex flex-wrap gap-2 px-3.5 pb-1.5'}>
{builtinColors.map((color, index) => (
<Tooltip key={index}>
<TooltipContent>{color.label}</TooltipContent>
<TooltipTrigger asChild>
<ColorTile
value={renderColor(color.color)}
active={singleColor === color.color}
onClick={() => handlePickColor(color.color)}
/>
</TooltipTrigger>
</Tooltip>
))}
</div>
<Separator className={'my-2'} />
<div className={'px-3.5 pb-2 pt-1.5 text-xs font-medium text-text-tertiary'}>{t('colors.custom')}</div>
<div className='flex gap-2 px-3.5 pb-1.5'>
{customColors.map((color, index) => (
<ColorTile
key={index}
value={renderColor(color)}
active={singleColor === color}
onClick={() => handlePickColor(color)}
/>
))}
<CreateCustomColorTile onClick={() => setIsChoosingCustom(true)} />
</div>
</div>
);
}, [
builtinColors,
customColors,
getRawColorValue,
handleCreateCustomColor,
handlePickColor,
isChoosingCustom,
recentColors,
singleColor,
t,
]);
return (
<Popover
open={isOpen}
onOpenChange={(open) => {
if (!open) {
saveRecentColors();
focusEditor(50);
}
setIsOpen(open);
}}
>
<PopoverTrigger asChild>
<ActionButton
active={!!singleColor}
tooltip={t('editor.backgroundColor')}
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
forceShow(!isOpen);
if (isOpen) {
saveRecentColors();
focusEditor();
} else {
handleOpen();
toggleDisableEditorFocus();
}
setIsOpen(!isOpen);
}}
>
<ColorSvg className='h-4 w-4' />
</ActionButton>
</PopoverTrigger>
<PopoverContent
className='!min-w-[236px]'
sideOffset={6}
align='start'
onMouseUp={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
onFocusCapture={(e) => e.stopPropagation()}
onOpenAutoFocus={() => {
window.getSelection()?.removeAllRanges();
toggleDisableEditorFocus();
}}
onCloseAutoFocus={() => {
focusEditor(0);
}}
onFocusOutside={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
{popoverContent}
</PopoverContent>
</Popover>
);
}
export default BgColor;

View File

@@ -1,251 +0,0 @@
import { YjsEditor } from '@/application/slate-yjs';
import { CustomEditor } from '@/application/slate-yjs/command';
import { EditorMarkFormat } from '@/application/slate-yjs/types';
import { ReactComponent as TextSvg } from '@/assets/icons/format_text.svg';
import { ReactComponent as ColorSvg } from '@/assets/icons/text_color.svg';
import { useSelectionToolbarContext } from '@/components/editor/components/toolbar/selection-toolbar/SelectionToolbar.hooks';
import { Popover } from '@/components/_shared/popover';
import { ColorEnum, renderColor } from '@/utils/color';
import { Tooltip } from '@mui/material';
import React, { useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useSlateStatic } from 'slate-react';
import ActionButton from './ActionButton';
function Color() {
const { t } = useTranslation();
const { visible: toolbarVisible } = useSelectionToolbarContext();
const editor = useSlateStatic() as YjsEditor;
const isActivated =
CustomEditor.isMarkActive(editor, EditorMarkFormat.BgColor) ||
CustomEditor.isMarkActive(editor, EditorMarkFormat.FontColor);
const marks = CustomEditor.getAllMarks(editor);
const activeBgColor = marks.find((mark) => mark[EditorMarkFormat.BgColor])?.[EditorMarkFormat.BgColor];
const activeFontColor = marks.find((mark) => mark[EditorMarkFormat.FontColor])?.[EditorMarkFormat.FontColor];
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null);
const open = Boolean(anchorEl);
useEffect(() => {
if (!toolbarVisible) {
setAnchorEl(null);
}
}, [toolbarVisible]);
const onClick = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
e.preventDefault();
setAnchorEl(e.currentTarget);
}, []);
const handleClose = useCallback(() => {
setAnchorEl(null);
}, []);
const handlePickedColor = useCallback(
(format: EditorMarkFormat, color: string) => {
if (color) {
CustomEditor.addMark(editor, {
key: format,
value: color,
});
} else {
CustomEditor.removeMark(editor, format);
}
},
[editor]
);
const editorTextColors = useMemo(() => {
return [
{
label: t('editor.fontColorDefault'),
color: '',
},
{
label: t('editor.fontColorGray'),
color: 'rgb(120, 119, 116)',
},
{
label: t('editor.fontColorBrown'),
color: 'rgb(159, 107, 83)',
},
{
label: t('editor.fontColorOrange'),
color: 'rgb(217, 115, 13)',
},
{
label: t('editor.fontColorYellow'),
color: 'rgb(203, 145, 47)',
},
{
label: t('editor.fontColorGreen'),
color: 'rgb(68, 131, 97)',
},
{
label: t('editor.fontColorBlue'),
color: 'rgb(51, 126, 169)',
},
{
label: t('editor.fontColorPurple'),
color: 'rgb(144, 101, 176)',
},
{
label: t('editor.fontColorPink'),
color: 'rgb(193, 76, 138)',
},
{
label: t('editor.fontColorRed'),
color: 'rgb(212, 76, 71)',
},
];
}, [t]);
const editorBgColors = useMemo(() => {
return [
{
label: t('editor.backgroundColorDefault'),
color: '',
},
{
label: t('editor.backgroundColorLime'),
color: ColorEnum.Lime,
},
{
label: t('editor.backgroundColorAqua'),
color: ColorEnum.Aqua,
},
{
label: t('editor.backgroundColorOrange'),
color: ColorEnum.Orange,
},
{
label: t('editor.backgroundColorYellow'),
color: ColorEnum.Yellow,
},
{
label: t('editor.backgroundColorGreen'),
color: ColorEnum.Green,
},
{
label: t('editor.backgroundColorBlue'),
color: ColorEnum.Blue,
},
{
label: t('editor.backgroundColorPurple'),
color: ColorEnum.Purple,
},
{
label: t('editor.backgroundColorPink'),
color: ColorEnum.Pink,
},
{
label: t('editor.backgroundColorRed'),
color: ColorEnum.LightPink,
},
];
}, [t]);
const popoverContent = useMemo(() => {
return (
<div className={'flex w-[200px] flex-col gap-3 p-3'}>
<div className={'flex flex-col gap-2'}>
<div className={'text-xs text-text-secondary'}>{t('editor.textColor')}</div>
<div className={'flex flex-wrap gap-1.5'}>
{editorTextColors.map((color, index) => {
return (
<Tooltip disableInteractive={true} key={index} title={color.label} placement={'top'}>
<div
className={'relative flex h-6 w-6 items-center justify-center'}
onClick={() => handlePickedColor(EditorMarkFormat.FontColor, color.color)}
style={{
color: color.color || 'var(--text-primary)',
}}
>
<div
className={`absolute left-0 top-0 h-full w-full cursor-pointer rounded-[6px] border-2 opacity-50 hover:opacity-100`}
style={{
borderColor: activeFontColor === color.color ? 'var(--text-action)' : undefined,
color: renderColor(color.color) || 'var(--text-primary)',
opacity: color.color ? undefined : 1,
}}
/>
<TextSvg />
</div>
</Tooltip>
);
})}
</div>
</div>
<div className={'flex flex-col gap-2'}>
<div className={'text-xs text-text-secondary'}>{t('editor.backgroundColor')}</div>
<div className={'flex flex-wrap gap-1.5'}>
{editorBgColors.map((color, index) => {
return (
<Tooltip disableInteractive={true} key={index} title={color.label} placement={'top'}>
<div
key={index}
className={
'relative flex h-6 w-6 cursor-pointer items-center justify-center overflow-hidden rounded-[6px]'
}
onClick={() => handlePickedColor(EditorMarkFormat.BgColor, color.color)}
>
<div
className={`absolute left-0 top-0 h-full w-full rounded-[6px] border-2`}
style={{
borderColor: activeBgColor === color.color ? 'var(--text-action)' : undefined,
}}
/>
<div
className={'z-[1] h-full w-full opacity-50 hover:opacity-100'}
style={{
backgroundColor: renderColor(color.color),
}}
/>
</div>
</Tooltip>
);
})}
</div>
</div>
</div>
);
}, [activeBgColor, activeFontColor, editorBgColors, editorTextColors, handlePickedColor, t]);
return (
<>
<ActionButton onClick={onClick} active={isActivated} tooltip={t('editor.color')}>
<ColorSvg className='h-4 w-4' />
</ActionButton>
{toolbarVisible && (
<Popover
onMouseDown={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onMouseUp={(e) => {
e.stopPropagation();
}}
disableRestoreFocus={true}
disableAutoFocus={true}
disableEnforceFocus={true}
open={open}
onClose={handleClose}
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: -8,
horizontal: 'center',
}}
>
{popoverContent}
</Popover>
)}
</>
);
}
export default Color;

View File

@@ -0,0 +1,429 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSlateStatic } from 'slate-react';
import { YjsEditor } from '@/application/slate-yjs';
import { CustomEditor } from '@/application/slate-yjs/command';
import { EditorMarkFormat } from '@/application/slate-yjs/types';
import { SubscriptionPlan } from '@/application/types';
import { ReactComponent as AddIcon } from '@/assets/icons/plus.svg';
import { ReactComponent as ColorSvg } from '@/assets/icons/text_color.svg';
import { ColorTile } from '@/components/_shared/color-picker';
import { CustomColorPicker } from '@/components/_shared/color-picker/CustomColorPicker';
import { useSelectionToolbarContext } from '@/components/editor/components/toolbar/selection-toolbar/SelectionToolbar.hooks';
import { useEditorContext } from '@/components/editor/EditorContext';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { Separator } from '@/components/ui/separator';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { renderColor } from '@/utils/color';
import ActionButton from './ActionButton';
function TextColor({
focusEditor,
toggleDisableEditorFocus,
}: {
focusEditor: (timeout?: number) => void;
toggleDisableEditorFocus: () => void;
}) {
const { t } = useTranslation();
const { getSubscriptions } = useEditorContext();
const { visible, forceShow } = useSelectionToolbarContext();
const editor = useSlateStatic() as YjsEditor;
const marks = CustomEditor.getAllMarks(editor).map(
(mark) => mark[EditorMarkFormat.FontToken] || mark[EditorMarkFormat.FontColor] || ''
);
const unique = [...new Set(marks)];
const singleColor = unique.length === 1 ? unique[0] : undefined;
const [isOpen, setIsOpen] = useState(false);
const [isChoosingCustom, setIsChoosingCustom] = useState(false);
const [recentColors, setRecentColors] = useState<string[]>([]);
const [customColors, setCustomColors] = useState<string[]>([]);
const recentColorToSave = useRef<string | null>(null);
const initialColor = useRef<string | null>(null);
const [activeSubscriptionPlan, setActiveSubscriptionPlan] = useState<SubscriptionPlan | null>(null);
const isPro = activeSubscriptionPlan === SubscriptionPlan.Pro;
const loadSubscription = useCallback(async () => {
try {
const subscriptions = await getSubscriptions?.();
if (!subscriptions || subscriptions.length === 0) {
setActiveSubscriptionPlan(SubscriptionPlan.Free);
return;
}
const subscription = subscriptions[0];
setActiveSubscriptionPlan(subscription?.plan || SubscriptionPlan.Free);
} catch (e) {
setActiveSubscriptionPlan(SubscriptionPlan.Free);
console.error(e);
}
}, [getSubscriptions]);
const isCustomColor = useCallback((color: string) => {
return color.startsWith('#') || color.startsWith('0x');
}, []);
const loadRecentAndCustomColors = useCallback(() => {
const recent = localStorage.getItem('recent-text-colors');
const custom = localStorage.getItem('custom-text-colors');
try {
const recentParsed: string[] = recent ? JSON.parse(recent) : [];
setRecentColors(recentParsed.slice(0, 6));
} catch (e) {
console.error('Failed to parse recent colors:', e);
}
try {
const customParsed: string[] = custom ? JSON.parse(custom) : [];
let updatedCustomColors = customParsed.slice(0, 5);
if (singleColor !== undefined && isCustomColor(singleColor) && !updatedCustomColors.includes(singleColor)) {
updatedCustomColors = [singleColor, ...updatedCustomColors].slice(0, 5);
}
setCustomColors(updatedCustomColors);
} catch (e) {
console.error('Failed to parse recent colors:', e);
}
}, [isCustomColor, singleColor]);
useEffect(() => {
if (!visible && isOpen) {
setIsOpen(false);
}
}, [isOpen, visible]);
useEffect(() => {
void loadSubscription();
}, [loadSubscription]);
const getRawColorValue = useCallback(
(color: string) => {
if (isCustomColor(color)) {
return color;
}
const computedStyle = window.getComputedStyle(document.documentElement);
if (color === '' || color === 'text-default') {
return computedStyle.getPropertyValue('--text-primary');
}
return computedStyle.getPropertyValue(`--palette-${color}`);
},
[isCustomColor]
);
const handlePickColor = useCallback(
(color: string) => {
if (color === '' || color === 'text-default' || color === singleColor) {
CustomEditor.removeMark(editor, EditorMarkFormat.FontColor);
CustomEditor.removeMark(editor, EditorMarkFormat.FontToken);
forceShow(true);
return;
}
if (isCustomColor(color)) {
CustomEditor.addMark(editor, {
key: EditorMarkFormat.FontColor,
value: color,
});
CustomEditor.removeMark(editor, EditorMarkFormat.FontToken);
} else {
CustomEditor.addMark(editor, {
key: EditorMarkFormat.FontColor,
value: window.getComputedStyle(document.documentElement).getPropertyValue(`--palette-${color}`),
});
CustomEditor.addMark(editor, {
key: EditorMarkFormat.FontToken,
value: color,
});
}
forceShow(true);
recentColorToSave.current = color;
},
[editor, forceShow, isCustomColor, singleColor]
);
const handleCreateCustomColor = useCallback(
(color: string) => {
if (!color || customColors.includes(color)) {
return;
}
const updatedCustomColors = [...customColors, color].slice(-5);
setCustomColors(updatedCustomColors);
localStorage.setItem('custom-text-colors', JSON.stringify(updatedCustomColors));
},
[customColors]
);
const saveRecentColors = useCallback(() => {
if (!recentColorToSave.current) {
return;
}
const color = recentColorToSave.current;
if (color && color !== initialColor.current) {
const updated = [color, ...recentColors.filter((c) => c !== color)].slice(0, 6);
localStorage.setItem('recent-text-colors', JSON.stringify(updated));
}
recentColorToSave.current = null;
}, [recentColors]);
const builtinColors = useMemo(() => {
return isPro
? [
{
label: t('colors.default'),
color: '',
},
{
label: t('colors.smoke'),
color: 'text-color-19',
},
{
label: t('colors.mallow'),
color: 'text-color-17',
},
{
label: t('colors.lavender'),
color: 'text-color-15',
},
{
label: t('colors.denim'),
color: 'text-color-13',
},
{
label: t('colors.aqua'),
color: 'text-color-11',
},
{
label: t('colors.forest'),
color: 'text-color-9',
},
{
label: t('colors.lime'),
color: 'text-color-7',
},
{
label: t('colors.lemon'),
color: 'text-color-5',
},
{
label: t('colors.tangerine'),
color: 'text-color-3',
},
{
label: t('colors.rose'),
color: 'text-color-1',
},
]
: [
{
label: t('colors.default'),
color: '',
},
{
label: t('colors.smoke'),
color: 'text-color-19',
},
{
label: t('colors.mallow'),
color: 'text-color-17',
},
{
label: t('colors.lavender'),
color: 'text-color-15',
},
{
label: t('colors.aqua'),
color: 'text-color-11',
},
{
label: t('colors.lime'),
color: 'text-color-7',
},
{
label: t('colors.lemon'),
color: 'text-color-5',
},
{
label: t('colors.tangerine'),
color: 'text-color-3',
},
];
}, [isPro, t]);
const handleOpen = useCallback(() => {
setIsChoosingCustom(false);
initialColor.current = singleColor || null;
loadRecentAndCustomColors();
}, [loadRecentAndCustomColors, singleColor]);
const popoverContent = useMemo(() => {
if (isChoosingCustom) {
return (
<div className='w-[236px] p-3'>
<CustomColorPicker
value={getRawColorValue(singleColor ?? '#ffffff')}
onCancel={() => setIsChoosingCustom(false)}
onApply={(color) => {
handleCreateCustomColor(color);
handlePickColor(color);
setIsChoosingCustom(false);
}}
/>
</div>
);
}
return (
<div className={'flex w-[236px] flex-col py-1.5'}>
{recentColors.length > 0 && (
<>
<div className={'px-3.5 pb-2 pt-1.5 text-xs font-medium text-text-tertiary'}>{t('colors.recent')}</div>
<div className={'flex gap-2 px-3.5 pb-1.5'}>
{recentColors.map((color, index) => (
<ColorTile
isText
key={index}
value={renderColor(color)}
active={singleColor === color}
onClick={() => handlePickColor(color)}
/>
))}
</div>
<Separator className={'my-2'} />
</>
)}
<div className={'px-3.5 pb-2 pt-1.5 text-xs font-medium text-text-tertiary'}>{t('editor.textColor')}</div>
<div className={'flex flex-wrap gap-2 px-3.5 pb-1.5'}>
{builtinColors.map((color, index) => (
<Tooltip key={index}>
<TooltipContent>{color.label}</TooltipContent>
<TooltipTrigger asChild>
<ColorTile
isText
value={renderColor(color.color)}
active={singleColor === color.color}
onClick={() => handlePickColor(color.color)}
/>
</TooltipTrigger>
</Tooltip>
))}
</div>
<Separator className={'my-2'} />
<div className={'px-3.5 pb-2 pt-1.5 text-xs font-medium text-text-tertiary'}>{t('colors.custom')}</div>
<div className='flex gap-2 px-3.5 pb-1.5'>
{customColors.map((color, index) => (
<ColorTile
isText
key={index}
value={renderColor(color)}
active={singleColor === color}
onClick={() => handlePickColor(color)}
/>
))}
<CreateCustomColorTile onClick={() => setIsChoosingCustom(true)} />
</div>
</div>
);
}, [
builtinColors,
customColors,
getRawColorValue,
handleCreateCustomColor,
handlePickColor,
isChoosingCustom,
recentColors,
singleColor,
t,
]);
return (
<Popover
open={isOpen}
onOpenChange={(open) => {
if (!open) {
saveRecentColors();
focusEditor(50);
}
setIsOpen(open);
}}
>
<PopoverTrigger asChild>
<ActionButton
active={!!singleColor}
tooltip={t('editor.textColor')}
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
forceShow(!isOpen);
if (isOpen) {
saveRecentColors();
focusEditor(0);
} else {
handleOpen();
toggleDisableEditorFocus();
}
setIsOpen(!isOpen);
}}
>
<ColorSvg className='h-4 w-4' />
</ActionButton>
</PopoverTrigger>
<PopoverContent
className='!min-w-[236px]'
sideOffset={6}
align='start'
onMouseUp={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
onFocusCapture={(e) => e.stopPropagation()}
onOpenAutoFocus={() => {
window.getSelection()?.removeAllRanges();
toggleDisableEditorFocus();
}}
onCloseAutoFocus={() => {
focusEditor(0);
}}
onFocusOutside={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
{popoverContent}
</PopoverContent>
</Popover>
);
}
export default TextColor;
export function CreateCustomColorTile({ onClick }: { onClick?: () => void }) {
return (
<div
onClick={onClick}
className={
'flex h-7 w-7 cursor-pointer items-center justify-center rounded-[6px] border border-border-primary hover:border-border-primary-hover'
}
>
<AddIcon className='h-5 w-5 text-icon-tertiary' />
</div>
);
}

View File

@@ -1,7 +1,17 @@
import { IconButton } from '@mui/material';
import { colorMap } from '@/utils/color';
import { ColorEnum, colorMap } from '@/utils/color';
const colors = Object.entries(colorMap);
const colors = [
[ColorEnum.Purple, colorMap[ColorEnum.Purple]],
[ColorEnum.Pink, colorMap[ColorEnum.Pink]],
[ColorEnum.LightPink, colorMap[ColorEnum.LightPink]],
[ColorEnum.Orange, colorMap[ColorEnum.Orange]],
[ColorEnum.Yellow, colorMap[ColorEnum.Yellow]],
[ColorEnum.Lime, colorMap[ColorEnum.Lime]],
[ColorEnum.Green, colorMap[ColorEnum.Green]],
[ColorEnum.Aqua, colorMap[ColorEnum.Aqua]],
[ColorEnum.Blue, colorMap[ColorEnum.Blue]],
];
function Colors ({ onDone }: { onDone?: (value: string) => void }) {
return (

View File

@@ -23,6 +23,8 @@ interface EditorInlineAttributes {
reminder_id?: string;
reminder_option?: string;
};
af_text_color?: string;
af_background_color?: string;
}
type CustomElement = {

View File

@@ -3,7 +3,7 @@
* AUTO-GENERATED FILE - DO NOT EDIT DIRECTLY
*
* Generated from: semantic.dark.json
* Generated time: 2025-06-24T06:01:38.064Z
* Generated time: 2025-07-30T06:18:37.482Z
*
* To modify these values, edit the source JSON files and run the token conversion script:
* node scripts/system-token/convert-tokens.cjs
@@ -35,7 +35,7 @@
--icon-primary: #e4e8f5;
--icon-secondary: #b5bbd3;
--icon-tertiary: #6f748c;
--icon-quaternary: #21232a;
--icon-quaternary: #54596e;
--icon-info-thick: #00b5ff;
--icon-info-thick-hover: #4ec1ff;
--icon-success-thick: #48a982;
@@ -104,7 +104,7 @@
--surface-primary-hover: #3d404f;
--surface-layer-01: #272930;
--surface-layer-01-hover: #3d404f;
--surface-layer-02: #32343f;
--surface-layer-02: #2b2d37;
--surface-layer-02-hover: #3d404f;
--surface-layer-03: #32343f;
--surface-layer-03-hover: #3d404f;
@@ -119,103 +119,114 @@
--background-primary: #21232a;
--badge-color-1-light-1: #edadad;
--badge-color-1-light-2: #cc4e4e;
--badge-color-1-light-3: #702828;
--badge-color-1-light-3: #9e3b3b;
--badge-color-1-thick-1: #fcf2f2;
--badge-color-1-thick-2: #fae3e3;
--badge-color-1-thick-3: #fad9d9;
--badge-color-1-thick-4: #9e3b3b;
--badge-color-2-light-1: #f0bda3;
--badge-color-2-light-2: #d67240;
--badge-color-2-light-3: #6b3215;
--badge-color-2-light-3: #a54b24;
--badge-color-2-thick-1: #fcf4f0;
--badge-color-2-thick-2: #fae8de;
--badge-color-2-thick-3: #fadfd2;
--badge-color-2-thick-4: #a54b24;
--badge-color-3-light-1: #f2cb99;
--badge-color-3-light-2: #db8f2c;
--badge-color-3-light-3: #613b0a;
--badge-color-3-light-3: #9a570c;
--badge-color-3-thick-1: #fff7ed;
--badge-color-3-thick-2: #fcedd9;
--badge-color-3-thick-3: #fae5ca;
--badge-color-3-thick-4: #9a570c;
--badge-color-4-light-1: #f5d68e;
--badge-color-4-light-2: #e0a416;
--badge-color-4-light-3: #5c4102;
--badge-color-4-light-3: #906000;
--badge-color-4-thick-1: #fff9ec;
--badge-color-4-thick-2: #fcf1d7;
--badge-color-4-thick-3: #fae9c3;
--badge-color-4-thick-4: #906000;
--badge-color-5-light-1: #f5e282;
--badge-color-5-light-2: #e0bb00;
--badge-color-5-light-3: #574800;
--badge-color-5-light-3: #866800;
--badge-color-5-thick-1: #fffbe8;
--badge-color-5-thick-2: #fcf5cf;
--badge-color-5-thick-3: #faefb9;
--badge-color-5-thick-4: #866800;
--badge-color-6-light-1: #dbde83;
--badge-color-6-light-2: #adb204;
--badge-color-6-light-3: #4a4c03;
--badge-color-6-light-3: #627300;
--badge-color-6-thick-1: #f9fae6;
--badge-color-6-thick-2: #f6f7d0;
--badge-color-6-thick-3: #f0f2b3;
--badge-color-6-thick-4: #627300;
--badge-color-7-light-1: #cfdb91;
--badge-color-7-light-2: #92a822;
--badge-color-7-light-3: #414d05;
--badge-color-7-light-3: #637000;
--badge-color-7-thick-1: #f6f9e6;
--badge-color-7-thick-2: #eef5ce;
--badge-color-7-thick-3: #e7f0bb;
--badge-color-7-thick-4: #637000;
--badge-color-8-light-1: #bfd998;
--badge-color-8-light-2: #75a828;
--badge-color-8-light-3: #334d0c;
--badge-color-8-light-3: #456514;
--badge-color-8-thick-1: #f4faeb;
--badge-color-8-thick-2: #e9f5d7;
--badge-color-8-thick-3: #def0c5;
--badge-color-8-thick-4: #456514;
--badge-color-9-light-1: #a8d6a1;
--badge-color-9-light-2: #49a33b;
--badge-color-9-light-3: #1e4f16;
--badge-color-9-light-3: #2b6c20;
--badge-color-9-thick-1: #f1faf0;
--badge-color-9-thick-2: #e2f5df;
--badge-color-9-thick-3: #d7f0d3;
--badge-color-9-thick-4: #2b6c20;
--badge-color-10-light-1: #90d1b5;
--badge-color-10-light-2: #1c9963;
--badge-color-10-light-3: #075231;
--badge-color-10-light-3: #127b47;
--badge-color-10-thick-1: #f0faf6;
--badge-color-10-thick-2: #dff5eb;
--badge-color-10-thick-3: #cef0e1;
--badge-color-10-thick-4: #127b47;
--badge-color-11-light-1: #83ccd4;
--badge-color-11-light-2: #008e9e;
--badge-color-11-light-3: #004e57;
--badge-color-11-light-3: #00727e;
--badge-color-11-thick-1: #f0f9fa;
--badge-color-11-thick-2: #dff3f5;
--badge-color-11-thick-3: #ccecf0;
--badge-color-11-thick-4: #00727e;
--badge-color-12-light-1: #88c0eb;
--badge-color-12-light-2: #0877cc;
--badge-color-12-light-3: #154469;
--badge-color-12-light-3: #0c5b9e;
--badge-color-12-thick-1: #f0f6fa;
--badge-color-12-thick-2: #e1eef7;
--badge-color-12-thick-3: #d3e6f5;
--badge-color-13-light-1: #9ab6ed;
--badge-color-13-light-2: #3267d1;
--badge-color-13-light-3: #223c70;
--badge-color-13-light-3: #2a51a0;
--badge-color-13-thick-1: #f0f3fa;
--badge-color-13-thick-2: #e3ebfa;
--badge-color-13-thick-3: #d7e2f7;
--badge-color-14-light-1: #aeaef5;
--badge-color-14-light-2: #5555e0;
--badge-color-14-light-3: #36366b;
--badge-color-14-light-3: #4545a6;
--badge-color-14-thick-1: #f2f2fc;
--badge-color-14-thick-2: #e6e6fa;
--badge-color-14-thick-3: #dcdcf7;
--badge-color-15-light-1: #c1aaf0;
--badge-color-15-light-2: #8153db;
--badge-color-15-light-3: #462f75;
--badge-color-15-light-3: #6435a8;
--badge-color-15-thick-1: #f6f3fc;
--badge-color-15-thick-2: #ebe3fa;
--badge-color-15-thick-3: #e4daf7;
--badge-color-16-light-1: #d3a9e8;
--badge-color-16-light-2: #9e4cc7;
--badge-color-16-light-3: #562d6b;
--badge-color-16-light-3: #772f96;
--badge-color-16-thick-1: #f7f0fa;
--badge-color-16-thick-2: #f0e1f7;
--badge-color-16-thick-3: #edd7f7;
--badge-color-17-light-1: #dea4dc;
--badge-color-17-light-2: #b240af;
--badge-color-17-light-3: #632861;
--badge-color-17-light-3: #8a3485;
--badge-color-17-thick-1: #faf0fa;
--badge-color-17-thick-2: #f5e1f4;
--badge-color-17-thick-3: #f5d7f4;
@@ -227,16 +238,56 @@
--badge-color-18-thick-3: #dedede;
--badge-color-19-light-1: #b8b8b8;
--badge-color-19-light-2: #6e6e6e;
--badge-color-19-light-3: #404040;
--badge-color-19-light-3: #585858;
--badge-color-19-thick-1: #f5f5f5;
--badge-color-19-thick-2: #e8e8e8;
--badge-color-19-thick-3: #dedede;
--badge-color-20-light-1: #b0b5bf;
--badge-color-20-light-2: #666f80;
--badge-color-20-light-3: #394152;
--badge-color-20-light-3: #4c5966;
--badge-color-20-thick-1: #f2f4f7;
--badge-color-20-thick-2: #e6e9f0;
--badge-color-20-thick-3: #dadee5;
--palette-text-color-1: #edadad;
--palette-text-color-2: #f0bda3;
--palette-text-color-3: #f2cb99;
--palette-text-color-4: #f5d68e;
--palette-text-color-5: #e0bb00;
--palette-text-color-6: #adb204;
--palette-text-color-7: #cfdb91;
--palette-text-color-8: #bfd998;
--palette-text-color-9: #a8d6a1;
--palette-text-color-10: #90d1b5;
--palette-text-color-11: #83ccd4;
--palette-text-color-12: #88c0eb;
--palette-text-color-13: #9ab6ed;
--palette-text-color-14: #aeaef5;
--palette-text-color-15: #c1aaf0;
--palette-text-color-16: #d3a9e8;
--palette-text-color-17: #dea4dc;
--palette-text-color-18: #e5a3c0;
--palette-text-color-19: #b8b8b8;
--palette-text-color-20: #b0b5bf;
--palette-bg-color-1: #9e3b3b;
--palette-bg-color-2: #a54b24;
--palette-bg-color-3: #9a570c;
--palette-bg-color-4: #906000;
--palette-bg-color-5: #866800;
--palette-bg-color-6: #627300;
--palette-bg-color-7: #637000;
--palette-bg-color-8: #456514;
--palette-bg-color-9: #2b6c20;
--palette-bg-color-10: #127b47;
--palette-bg-color-11: #00727e;
--palette-bg-color-12: #0c5b9e;
--palette-bg-color-13: #2a51a0;
--palette-bg-color-14: #4545a6;
--palette-bg-color-15: #6435a8;
--palette-bg-color-16: #772f96;
--palette-bg-color-17: #8a3485;
--palette-bg-color-18: #6e2343;
--palette-bg-color-19: #585858;
--palette-bg-color-20: #4c5966;
--brand-skyline: #00b5ff;
--brand-aqua: #00c8ff;
--brand-violet: #9327ff;

View File

@@ -3,7 +3,7 @@
* AUTO-GENERATED FILE - DO NOT EDIT DIRECTLY
*
* Generated from: semantic.light.json
* Generated time: 2025-06-24T06:01:38.059Z
* Generated time: 2025-07-30T06:18:37.480Z
*
* To modify these values, edit the source JSON files and run the token conversion script:
* node scripts/system-token/convert-tokens.cjs
@@ -122,103 +122,114 @@
--badge-color-1-light-3: #fad9d9;
--badge-color-1-thick-1: #edadad;
--badge-color-1-thick-2: #cc4e4e;
--badge-color-1-thick-3: #702828;
--badge-color-1-thick-3: #9e3b3b;
--badge-color-1-thick-4: #100505;
--badge-color-2-light-1: #fcf4f0;
--badge-color-2-light-2: #fae8de;
--badge-color-2-light-3: #fadfd2;
--badge-color-2-thick-1: #f0bda3;
--badge-color-2-thick-2: #d67240;
--badge-color-2-thick-3: #6b3215;
--badge-color-2-thick-3: #a54b24;
--badge-color-2-thick-4: #33221b;
--badge-color-3-light-1: #fff7ed;
--badge-color-3-light-2: #fcedd9;
--badge-color-3-light-3: #fae5ca;
--badge-color-3-thick-1: #f2cb99;
--badge-color-3-thick-2: #db8f2c;
--badge-color-3-thick-3: #613b0a;
--badge-color-3-thick-3: #9a570c;
--badge-color-3-thick-4: #44341e;
--badge-color-4-light-1: #fff9ec;
--badge-color-4-light-2: #fcf1d7;
--badge-color-4-light-3: #fae9c3;
--badge-color-4-thick-1: #f5d68e;
--badge-color-4-thick-2: #e0a416;
--badge-color-4-thick-3: #5c4102;
--badge-color-4-thick-3: #906000;
--badge-color-4-thick-4: #4f4020;
--badge-color-5-light-1: #fffbe8;
--badge-color-5-light-2: #fcf5cf;
--badge-color-5-light-3: #faefb9;
--badge-color-5-thick-1: #f5e282;
--badge-color-5-thick-2: #e0bb00;
--badge-color-5-thick-3: #574800;
--badge-color-5-thick-3: #866800;
--badge-color-5-thick-4: #574d1c;
--badge-color-6-light-1: #f9fae6;
--badge-color-6-light-2: #f6f7d0;
--badge-color-6-light-3: #f0f2b3;
--badge-color-6-thick-1: #dbde83;
--badge-color-6-thick-2: #adb204;
--badge-color-6-thick-3: #4a4c03;
--badge-color-6-thick-3: #627300;
--badge-color-6-thick-4: #404207;
--badge-color-7-light-1: #f6f9e6;
--badge-color-7-light-2: #eef5ce;
--badge-color-7-light-3: #e7f0bb;
--badge-color-7-thick-1: #cfdb91;
--badge-color-7-thick-2: #92a822;
--badge-color-7-thick-3: #414d05;
--badge-color-7-thick-3: #637000;
--badge-color-7-thick-4: #313908;
--badge-color-8-light-1: #f4faeb;
--badge-color-8-light-2: #e9f5d7;
--badge-color-8-light-3: #def0c5;
--badge-color-8-thick-1: #bfd998;
--badge-color-8-thick-2: #75a828;
--badge-color-8-thick-3: #334d0c;
--badge-color-8-thick-3: #456514;
--badge-color-8-thick-4: #273611;
--badge-color-9-light-1: #f1faf0;
--badge-color-9-light-2: #e2f5df;
--badge-color-9-light-3: #d7f0d3;
--badge-color-9-thick-1: #a8d6a1;
--badge-color-9-thick-2: #49a33b;
--badge-color-9-thick-3: #1e4f16;
--badge-color-9-thick-3: #2b6c20;
--badge-color-9-thick-4: #1a2e17;
--badge-color-10-light-1: #f0faf6;
--badge-color-10-light-2: #dff5eb;
--badge-color-10-light-3: #cef0e1;
--badge-color-10-thick-1: #90d1b5;
--badge-color-10-thick-2: #1c9963;
--badge-color-10-thick-3: #075231;
--badge-color-10-thick-3: #127b47;
--badge-color-10-thick-4: #0f231a;
--badge-color-11-light-1: #f0f9fa;
--badge-color-11-light-2: #dff3f5;
--badge-color-11-light-3: #ccecf0;
--badge-color-11-thick-1: #83ccd4;
--badge-color-11-thick-2: #008e9e;
--badge-color-11-thick-3: #004e57;
--badge-color-11-thick-3: #00727e;
--badge-color-11-thick-4: #0d1a1c;
--badge-color-12-light-1: #f0f6fa;
--badge-color-12-light-2: #e1eef7;
--badge-color-12-light-3: #d3e6f5;
--badge-color-12-thick-1: #88c0eb;
--badge-color-12-thick-2: #0877cc;
--badge-color-12-thick-3: #154469;
--badge-color-12-thick-3: #0c5b9e;
--badge-color-13-light-1: #f0f3fa;
--badge-color-13-light-2: #e3ebfa;
--badge-color-13-light-3: #d7e2f7;
--badge-color-13-thick-1: #9ab6ed;
--badge-color-13-thick-2: #3267d1;
--badge-color-13-thick-3: #223c70;
--badge-color-13-thick-3: #2a51a0;
--badge-color-14-light-1: #f2f2fc;
--badge-color-14-light-2: #e6e6fa;
--badge-color-14-light-3: #dcdcf7;
--badge-color-14-thick-1: #aeaef5;
--badge-color-14-thick-2: #5555e0;
--badge-color-14-thick-3: #36366b;
--badge-color-14-thick-3: #4545a6;
--badge-color-15-light-1: #f6f3fc;
--badge-color-15-light-2: #ebe3fa;
--badge-color-15-light-3: #e4daf7;
--badge-color-15-thick-1: #c1aaf0;
--badge-color-15-thick-2: #8153db;
--badge-color-15-thick-3: #462f75;
--badge-color-15-thick-3: #6435a8;
--badge-color-16-light-1: #f7f0fa;
--badge-color-16-light-2: #f0e1f7;
--badge-color-16-light-3: #edd7f7;
--badge-color-16-thick-1: #d3a9e8;
--badge-color-16-thick-2: #9e4cc7;
--badge-color-16-thick-3: #562d6b;
--badge-color-16-thick-3: #772f96;
--badge-color-17-light-1: #faf0fa;
--badge-color-17-light-2: #f5e1f4;
--badge-color-17-light-3: #f5d7f4;
--badge-color-17-thick-1: #dea4dc;
--badge-color-17-thick-2: #b240af;
--badge-color-17-thick-3: #632861;
--badge-color-17-thick-3: #8a3485;
--badge-color-18-light-1: #f9eff3;
--badge-color-18-light-2: #f7e1eb;
--badge-color-18-light-3: #f7d7e5;
@@ -230,13 +241,53 @@
--badge-color-19-light-3: #dedede;
--badge-color-19-thick-1: #b8b8b8;
--badge-color-19-thick-2: #6e6e6e;
--badge-color-19-thick-3: #404040;
--badge-color-19-thick-3: #585858;
--badge-color-20-light-1: #f2f4f7;
--badge-color-20-light-2: #e6e9f0;
--badge-color-20-light-3: #dadee5;
--badge-color-20-thick-1: #b0b5bf;
--badge-color-20-thick-2: #666f80;
--badge-color-20-thick-3: #394152;
--badge-color-20-thick-3: #4c5966;
--palette-text-color-1: #cc4e4e;
--palette-text-color-2: #d67240;
--palette-text-color-3: #db8f2c;
--palette-text-color-4: #e0a416;
--palette-text-color-5: #e0bb00;
--palette-text-color-6: #adb204;
--palette-text-color-7: #92a822;
--palette-text-color-8: #75a828;
--palette-text-color-9: #49a33b;
--palette-text-color-10: #1c9963;
--palette-text-color-11: #008e9e;
--palette-text-color-12: #0877cc;
--palette-text-color-13: #3267d1;
--palette-text-color-14: #5555e0;
--palette-text-color-15: #8153db;
--palette-text-color-16: #9e4cc7;
--palette-text-color-17: #b240af;
--palette-text-color-18: #c24279;
--palette-text-color-19: #6e6e6e;
--palette-text-color-20: #666f80;
--palette-bg-color-1: #fad9d9;
--palette-bg-color-2: #fadfd2;
--palette-bg-color-3: #fae5ca;
--palette-bg-color-4: #fae9c3;
--palette-bg-color-5: #faefb9;
--palette-bg-color-6: #f0f2b3;
--palette-bg-color-7: #e7f0bb;
--palette-bg-color-8: #def0c5;
--palette-bg-color-9: #d7f0d3;
--palette-bg-color-10: #cef0e1;
--palette-bg-color-11: #ccecf0;
--palette-bg-color-12: #d3e6f5;
--palette-bg-color-13: #d7e2f7;
--palette-bg-color-14: #dcdcf7;
--palette-bg-color-15: #e4daf7;
--palette-bg-color-16: #edd7f7;
--palette-bg-color-17: #f5d7f4;
--palette-bg-color-18: #f7d7e5;
--palette-bg-color-19: #dedede;
--palette-bg-color-20: #dadee5;
--brand-skyline: #00b5ff;
--brand-aqua: #00c8ff;
--brand-violet: #9327ff;

View File

@@ -8,6 +8,48 @@ export enum ColorEnum {
Green = 'appflowy_them_color_tint7',
Aqua = 'appflowy_them_color_tint8',
Blue = 'appflowy_them_color_tint9',
DefaultText = 'text-default',
DefaultBackground = 'bg-default',
TextColor1 = 'text-color-1',
TextColor2 = 'text-color-2',
TextColor3 = 'text-color-3',
TextColor4 = 'text-color-4',
TextColor5 = 'text-color-5',
TextColor6 = 'text-color-6',
TextColor7 = 'text-color-7',
TextColor8 = 'text-color-8',
TextColor9 = 'text-color-9',
TextColor10 = 'text-color-10',
TextColor11 = 'text-color-11',
TextColor12 = 'text-color-12',
TextColor13 = 'text-color-13',
TextColor14 = 'text-color-14',
TextColor15 = 'text-color-15',
TextColor16 = 'text-color-16',
TextColor17 = 'text-color-17',
TextColor18 = 'text-color-18',
TextColor19 = 'text-color-19',
TextColor20 = 'text-color-20',
BgColor1 = 'bg-color-1',
BgColor2 = 'bg-color-2',
BgColor3 = 'bg-color-3',
BgColor4 = 'bg-color-4',
BgColor5 = 'bg-color-5',
BgColor6 = 'bg-color-6',
BgColor7 = 'bg-color-7',
BgColor8 = 'bg-color-8',
BgColor9 = 'bg-color-9',
BgColor10 = 'bg-color-10',
BgColor11 = 'bg-color-11',
BgColor12 = 'bg-color-12',
BgColor13 = 'bg-color-13',
BgColor14 = 'bg-color-14',
BgColor15 = 'bg-color-15',
BgColor16 = 'bg-color-16',
BgColor17 = 'bg-color-17',
BgColor18 = 'bg-color-18',
BgColor19 = 'bg-color-19',
BgColor20 = 'bg-color-20',
}
export enum GradientEnum {
@@ -30,6 +72,48 @@ export const colorMap = {
[ColorEnum.Green]: 'var(--tint-green)',
[ColorEnum.Aqua]: 'var(--tint-aqua)',
[ColorEnum.Blue]: 'var(--tint-blue)',
[ColorEnum.DefaultText]: 'var(--text-primary)',
[ColorEnum.DefaultBackground]: '',
[ColorEnum.TextColor1]: 'var(--palette-text-color-1)',
[ColorEnum.TextColor2]: 'var(--palette-text-color-2)',
[ColorEnum.TextColor3]: 'var(--palette-text-color-3)',
[ColorEnum.TextColor4]: 'var(--palette-text-color-4)',
[ColorEnum.TextColor5]: 'var(--palette-text-color-5)',
[ColorEnum.TextColor6]: 'var(--palette-text-color-6)',
[ColorEnum.TextColor7]: 'var(--palette-text-color-7)',
[ColorEnum.TextColor8]: 'var(--palette-text-color-8)',
[ColorEnum.TextColor9]: 'var(--palette-text-color-9)',
[ColorEnum.TextColor10]: 'var(--palette-text-color-10)',
[ColorEnum.TextColor11]: 'var(--palette-text-color-11)',
[ColorEnum.TextColor12]: 'var(--palette-text-color-12)',
[ColorEnum.TextColor13]: 'var(--palette-text-color-13)',
[ColorEnum.TextColor14]: 'var(--palette-text-color-14)',
[ColorEnum.TextColor15]: 'var(--palette-text-color-15)',
[ColorEnum.TextColor16]: 'var(--palette-text-color-16)',
[ColorEnum.TextColor17]: 'var(--palette-text-color-17)',
[ColorEnum.TextColor18]: 'var(--palette-text-color-18)',
[ColorEnum.TextColor19]: 'var(--palette-text-color-19)',
[ColorEnum.TextColor20]: 'var(--palette-text-color-20)',
[ColorEnum.BgColor1]: 'var(--palette-bg-color-1)',
[ColorEnum.BgColor2]: 'var(--palette-bg-color-2)',
[ColorEnum.BgColor3]: 'var(--palette-bg-color-3)',
[ColorEnum.BgColor4]: 'var(--palette-bg-color-4)',
[ColorEnum.BgColor5]: 'var(--palette-bg-color-5)',
[ColorEnum.BgColor6]: 'var(--palette-bg-color-6)',
[ColorEnum.BgColor7]: 'var(--palette-bg-color-7)',
[ColorEnum.BgColor8]: 'var(--palette-bg-color-8)',
[ColorEnum.BgColor9]: 'var(--palette-bg-color-9)',
[ColorEnum.BgColor10]: 'var(--palette-bg-color-10)',
[ColorEnum.BgColor11]: 'var(--palette-bg-color-11)',
[ColorEnum.BgColor12]: 'var(--palette-bg-color-12)',
[ColorEnum.BgColor13]: 'var(--palette-bg-color-13)',
[ColorEnum.BgColor14]: 'var(--palette-bg-color-14)',
[ColorEnum.BgColor15]: 'var(--palette-bg-color-15)',
[ColorEnum.BgColor16]: 'var(--palette-bg-color-16)',
[ColorEnum.BgColor17]: 'var(--palette-bg-color-17)',
[ColorEnum.BgColor18]: 'var(--palette-bg-color-18)',
[ColorEnum.BgColor19]: 'var(--palette-bg-color-19)',
[ColorEnum.BgColor20]: 'var(--palette-bg-color-20)',
};
export const gradientMap = {
@@ -45,7 +129,7 @@ export const gradientMap = {
// Convert ARGB to RGBA
// Flutter uses ARGB, but CSS uses RGBA
function argbToRgba(color: string): string {
if(!color) {
if (!color) {
return '';
}
@@ -53,7 +137,7 @@ function argbToRgba(color: string): string {
const hasAlpha = hex.length === 8;
if(!hasAlpha) {
if (!hasAlpha) {
return color.replace('0x', '#');
}
@@ -66,11 +150,11 @@ function argbToRgba(color: string): string {
}
export function renderColor(color: string) {
if(colorMap[color as ColorEnum]) {
if (colorMap[color as ColorEnum]) {
return colorMap[color as ColorEnum];
}
if(gradientMap[color as GradientEnum]) {
if (gradientMap[color as GradientEnum]) {
return gradientMap[color as GradientEnum];
}
@@ -82,17 +166,17 @@ export function stringToColor(string: string, colorArray?: string[]) {
let i;
/* eslint-disable no-bitwise */
for(i = 0; i < string.length; i += 1) {
for (i = 0; i < string.length; i += 1) {
hash = string.charCodeAt(i) + ((hash << 5) - hash);
}
if(colorArray) {
if (colorArray) {
return colorArray[string.slice(0, 1).charCodeAt(0) % colorArray.length];
}
let color = '#';
for(i = 0; i < 3; i += 1) {
for (i = 0; i < 3; i += 1) {
const value = (hash >> (i * 8)) & 0xff;
color += `00${value.toString(16)}`.slice(-2);
@@ -154,7 +238,7 @@ const colorDefaultArray: string[] = [
];
export function stringAvatar(name: string, colorArray: string[] = colorDefaultArray) {
if(!name) {
if (!name) {
return null;
}