mirror of
https://github.com/AppFlowy-IO/AppFlowy-Web.git
synced 2026-03-13 10:00:26 +08:00
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:
@@ -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",
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
|
||||
@@ -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 |
@@ -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 |
46
src/components/_shared/color-picker/ColorTile.tsx
Normal file
46
src/components/_shared/color-picker/ColorTile.tsx
Normal 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>
|
||||
);
|
||||
});
|
||||
132
src/components/_shared/color-picker/CustomColorPicker.tsx
Normal file
132
src/components/_shared/color-picker/CustomColorPicker.tsx
Normal 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)}`;
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export * from './ColorPicker';
|
||||
export * from './ColorPicker';
|
||||
export * from './ColorTile';
|
||||
|
||||
36
src/components/_shared/color-picker/style.css
Normal file
36
src/components/_shared/color-picker/style.css
Normal 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%);
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -66,9 +66,7 @@ export function useVisible() {
|
||||
}, [visible, removeDecorate]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
const handleMouseDown = () => {
|
||||
|
||||
const { selection } = editor;
|
||||
|
||||
if(selection && Range.isExpanded(selection)) {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
|
||||
2
src/slate-editor.d.ts
vendored
2
src/slate-editor.d.ts
vendored
@@ -23,6 +23,8 @@ interface EditorInlineAttributes {
|
||||
reminder_id?: string;
|
||||
reminder_option?: string;
|
||||
};
|
||||
af_text_color?: string;
|
||||
af_background_color?: string;
|
||||
}
|
||||
|
||||
type CustomElement = {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user