diff --git a/package.json b/package.json index 88c0b5ff..bab5c69a 100644 --- a/package.json +++ b/package.json @@ -103,6 +103,7 @@ "react-beautiful-dnd": "^13.1.1", "react-big-calendar": "^1.8.5", "react-color": "^2.19.3", + "react-colorful": "^5.6.1", "react-custom-scrollbars": "^4.2.1", "react-custom-scrollbars-2": "^4.5.0", "react-datepicker": "^4.23.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 689b81a4..d0c11668 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -245,6 +245,9 @@ importers: react-color: specifier: ^2.19.3 version: 2.19.3(react@18.3.1) + react-colorful: + specifier: ^5.6.1 + version: 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-custom-scrollbars: specifier: ^4.2.1 version: 4.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -7201,6 +7204,12 @@ packages: peerDependencies: react: '*' + react-colorful@5.6.1: + resolution: {integrity: sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + react-custom-scrollbars-2@4.5.0: resolution: {integrity: sha512-/z0nWAeXfMDr4+OXReTpYd1Atq9kkn4oI3qxq3iMXGQx1EEfwETSqB8HTAvg1X7dEqcCachbny1DRNGlqX5bDQ==} peerDependencies: @@ -16675,6 +16684,11 @@ snapshots: reactcss: 1.2.3(react@18.3.1) tinycolor2: 1.6.0 + react-colorful@5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-custom-scrollbars-2@4.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: dom-css: 2.1.0 diff --git a/scripts/system-token/primitive.json b/scripts/system-token/primitive.json index fe549474..aafd147c 100644 --- a/scripts/system-token/primitive.json +++ b/scripts/system-token/primitive.json @@ -48,6 +48,10 @@ "$type": "color", "$value": "#32343f" }, + "870": { + "$type": "color", + "$value": "#2b2d37" + }, "900": { "$type": "color", "$value": "#272930" @@ -428,7 +432,11 @@ }, "600": { "$type": "color", - "$value": "#702828" + "$value": "#9e3b3b" + }, + "700": { + "$type": "color", + "$value": "#100505" } }, "Papaya": { @@ -454,7 +462,11 @@ }, "600": { "$type": "color", - "$value": "#6b3215" + "$value": "#a54b24" + }, + "700": { + "$type": "color", + "$value": "#33221b" } }, "Tangerine": { @@ -480,7 +492,11 @@ }, "600": { "$type": "color", - "$value": "#613b0a" + "$value": "#9a570c" + }, + "700": { + "$type": "color", + "$value": "#44341e" } }, "Mango": { @@ -506,7 +522,11 @@ }, "600": { "$type": "color", - "$value": "#5c4102" + "$value": "#906000" + }, + "700": { + "$type": "color", + "$value": "#4f4020" } }, "Lemon": { @@ -532,7 +552,11 @@ }, "600": { "$type": "color", - "$value": "#574800" + "$value": "#866800" + }, + "700": { + "$type": "color", + "$value": "#574d1c" } }, "Olive": { @@ -558,7 +582,11 @@ }, "600": { "$type": "color", - "$value": "#4a4c03" + "$value": "#627300" + }, + "700": { + "$type": "color", + "$value": "#404207" } }, "Lime": { @@ -584,7 +612,11 @@ }, "600": { "$type": "color", - "$value": "#414d05" + "$value": "#637000" + }, + "700": { + "$type": "color", + "$value": "#313908" } }, "Grass": { @@ -610,7 +642,11 @@ }, "600": { "$type": "color", - "$value": "#334d0c" + "$value": "#456514" + }, + "700": { + "$type": "color", + "$value": "#273611" } }, "Forest": { @@ -636,7 +672,11 @@ }, "600": { "$type": "color", - "$value": "#1e4f16" + "$value": "#2b6c20" + }, + "700": { + "$type": "color", + "$value": "#1a2e17" } }, "Jade": { @@ -662,7 +702,11 @@ }, "600": { "$type": "color", - "$value": "#075231" + "$value": "#127b47" + }, + "700": { + "$type": "color", + "$value": "#0f231a" } }, "Aqua": { @@ -688,7 +732,11 @@ }, "600": { "$type": "color", - "$value": "#004e57" + "$value": "#00727e" + }, + "700": { + "$type": "color", + "$value": "#0d1a1c" } }, "Azure": { @@ -714,7 +762,7 @@ }, "600": { "$type": "color", - "$value": "#154469" + "$value": "#0c5b9e" } }, "Denim": { @@ -740,7 +788,7 @@ }, "600": { "$type": "color", - "$value": "#223c70" + "$value": "#2a51a0" } }, "Mauve": { @@ -766,7 +814,7 @@ }, "600": { "$type": "color", - "$value": "#36366b" + "$value": "#4545a6" } }, "Lavender": { @@ -792,7 +840,7 @@ }, "600": { "$type": "color", - "$value": "#462f75" + "$value": "#6435a8" } }, "Lilac": { @@ -818,7 +866,7 @@ }, "600": { "$type": "color", - "$value": "#562d6b" + "$value": "#772f96" } }, "Mallow": { @@ -844,7 +892,7 @@ }, "600": { "$type": "color", - "$value": "#632861" + "$value": "#8a3485" } }, "Camellia": { @@ -896,7 +944,7 @@ }, "600": { "$type": "color", - "$value": "#404040" + "$value": "#585858" } }, "Iron": { @@ -922,7 +970,7 @@ }, "600": { "$type": "color", - "$value": "#394152" + "$value": "#4c5966" } } }, diff --git a/scripts/system-token/semantic.dark.json b/scripts/system-token/semantic.dark.json index f7d7ef3a..439abbf5 100644 --- a/scripts/system-token/semantic.dark.json +++ b/scripts/system-token/semantic.dark.json @@ -104,7 +104,7 @@ }, "quaternary": { "$type": "color", - "$value": "{Neutral.1000}" + "$value": "{Neutral.700}" }, "info-thick": { "$type": "color", @@ -397,7 +397,7 @@ }, "layer-02": { "$type": "color", - "$value": "{Neutral.850}", + "$value": "{Neutral.870}", "$description": "Modal Window" }, "layer-02-hover": { @@ -482,6 +482,10 @@ "thick-3": { "$type": "color", "$value": "{Subtle_Color.Rose.300}" + }, + "thick-4": { + "$type": "color", + "$value": "{Subtle_Color.Rose.600}" } }, "color-2": { @@ -508,6 +512,10 @@ "thick-3": { "$type": "color", "$value": "{Subtle_Color.Papaya.300}" + }, + "thick-4": { + "$type": "color", + "$value": "{Subtle_Color.Papaya.600}" } }, "color-3": { @@ -534,6 +542,10 @@ "thick-3": { "$type": "color", "$value": "{Subtle_Color.Tangerine.300}" + }, + "thick-4": { + "$type": "color", + "$value": "{Subtle_Color.Tangerine.600}" } }, "color-4": { @@ -560,6 +572,10 @@ "thick-3": { "$type": "color", "$value": "{Subtle_Color.Mango.300}" + }, + "thick-4": { + "$type": "color", + "$value": "{Subtle_Color.Mango.600}" } }, "color-5": { @@ -586,6 +602,10 @@ "thick-3": { "$type": "color", "$value": "{Subtle_Color.Lemon.300}" + }, + "thick-4": { + "$type": "color", + "$value": "{Subtle_Color.Lemon.600}" } }, "color-6": { @@ -612,6 +632,10 @@ "thick-3": { "$type": "color", "$value": "{Subtle_Color.Olive.300}" + }, + "thick-4": { + "$type": "color", + "$value": "{Subtle_Color.Olive.600}" } }, "color-7": { @@ -638,6 +662,10 @@ "thick-3": { "$type": "color", "$value": "{Subtle_Color.Lime.300}" + }, + "thick-4": { + "$type": "color", + "$value": "{Subtle_Color.Lime.600}" } }, "color-8": { @@ -664,6 +692,10 @@ "thick-3": { "$type": "color", "$value": "{Subtle_Color.Grass.300}" + }, + "thick-4": { + "$type": "color", + "$value": "{Subtle_Color.Grass.600}" } }, "color-9": { @@ -690,6 +722,10 @@ "thick-3": { "$type": "color", "$value": "{Subtle_Color.Forest.300}" + }, + "thick-4": { + "$type": "color", + "$value": "{Subtle_Color.Forest.600}" } }, "color-10": { @@ -716,6 +752,10 @@ "thick-3": { "$type": "color", "$value": "{Subtle_Color.Jade.300}" + }, + "thick-4": { + "$type": "color", + "$value": "{Subtle_Color.Jade.600}" } }, "color-11": { @@ -742,6 +782,10 @@ "thick-3": { "$type": "color", "$value": "{Subtle_Color.Aqua.300}" + }, + "thick-4": { + "$type": "color", + "$value": "{Subtle_Color.Aqua.600}" } }, "color-12": { @@ -979,6 +1023,172 @@ } } }, + "Palette": { + "text": { + "color-1": { + "$type": "color", + "$value": "{Subtle_Color.Rose.400}" + }, + "color-2": { + "$type": "color", + "$value": "{Subtle_Color.Papaya.400}" + }, + "color-3": { + "$type": "color", + "$value": "{Subtle_Color.Tangerine.400}" + }, + "color-4": { + "$type": "color", + "$value": "{Subtle_Color.Mango.400}" + }, + "color-5": { + "$type": "color", + "$value": "{Subtle_Color.Lemon.500}" + }, + "color-6": { + "$type": "color", + "$value": "{Subtle_Color.Olive.500}" + }, + "color-7": { + "$type": "color", + "$value": "{Subtle_Color.Lime.400}" + }, + "color-8": { + "$type": "color", + "$value": "{Subtle_Color.Grass.400}" + }, + "color-9": { + "$type": "color", + "$value": "{Subtle_Color.Forest.400}" + }, + "color-10": { + "$type": "color", + "$value": "{Subtle_Color.Jade.400}" + }, + "color-11": { + "$type": "color", + "$value": "{Subtle_Color.Aqua.400}" + }, + "color-12": { + "$type": "color", + "$value": "{Subtle_Color.Azure.400}" + }, + "color-13": { + "$type": "color", + "$value": "{Subtle_Color.Denim.400}" + }, + "color-14": { + "$type": "color", + "$value": "{Subtle_Color.Mauve.400}" + }, + "color-15": { + "$type": "color", + "$value": "{Subtle_Color.Lavender.400}" + }, + "color-16": { + "$type": "color", + "$value": "{Subtle_Color.Lilac.400}" + }, + "color-17": { + "$type": "color", + "$value": "{Subtle_Color.Mallow.400}" + }, + "color-18": { + "$type": "color", + "$value": "{Subtle_Color.Camellia.400}" + }, + "color-19": { + "$type": "color", + "$value": "{Subtle_Color.Smoke.400}" + }, + "color-20": { + "$type": "color", + "$value": "{Subtle_Color.Iron.400}" + } + }, + "bg": { + "color-1": { + "$type": "color", + "$value": "{Subtle_Color.Rose.600}" + }, + "color-2": { + "$type": "color", + "$value": "{Subtle_Color.Papaya.600}" + }, + "color-3": { + "$type": "color", + "$value": "{Subtle_Color.Tangerine.600}" + }, + "color-4": { + "$type": "color", + "$value": "{Subtle_Color.Mango.600}" + }, + "color-5": { + "$type": "color", + "$value": "{Subtle_Color.Lemon.600}" + }, + "color-6": { + "$type": "color", + "$value": "{Subtle_Color.Olive.600}" + }, + "color-7": { + "$type": "color", + "$value": "{Subtle_Color.Lime.600}" + }, + "color-8": { + "$type": "color", + "$value": "{Subtle_Color.Grass.600}" + }, + "color-9": { + "$type": "color", + "$value": "{Subtle_Color.Forest.600}" + }, + "color-10": { + "$type": "color", + "$value": "{Subtle_Color.Jade.600}" + }, + "color-11": { + "$type": "color", + "$value": "{Subtle_Color.Aqua.600}" + }, + "color-12": { + "$type": "color", + "$value": "{Subtle_Color.Azure.600}" + }, + "color-13": { + "$type": "color", + "$value": "{Subtle_Color.Denim.600}" + }, + "color-14": { + "$type": "color", + "$value": "{Subtle_Color.Mauve.600}" + }, + "color-15": { + "$type": "color", + "$value": "{Subtle_Color.Lavender.600}" + }, + "color-16": { + "$type": "color", + "$value": "{Subtle_Color.Lilac.600}" + }, + "color-17": { + "$type": "color", + "$value": "{Subtle_Color.Mallow.600}" + }, + "color-18": { + "$type": "color", + "$value": "{Subtle_Color.Camellia.600}" + }, + "color-19": { + "$type": "color", + "$value": "{Subtle_Color.Smoke.600}" + }, + "color-20": { + "$type": "color", + "$value": "{Subtle_Color.Iron.600}" + } + } + }, "Brand": { "Skyline": { "$type": "color", diff --git a/scripts/system-token/semantic.light.json b/scripts/system-token/semantic.light.json index 0c6f9c6b..d5d2bc82 100644 --- a/scripts/system-token/semantic.light.json +++ b/scripts/system-token/semantic.light.json @@ -482,6 +482,10 @@ "thick-3": { "$type": "color", "$value": "{Subtle_Color.Rose.600}" + }, + "thick-4": { + "$type": "color", + "$value": "{Subtle_Color.Rose.700}" } }, "color-2": { @@ -508,6 +512,10 @@ "thick-3": { "$type": "color", "$value": "{Subtle_Color.Papaya.600}" + }, + "thick-4": { + "$type": "color", + "$value": "{Subtle_Color.Papaya.700}" } }, "color-3": { @@ -534,6 +542,10 @@ "thick-3": { "$type": "color", "$value": "{Subtle_Color.Tangerine.600}" + }, + "thick-4": { + "$type": "color", + "$value": "{Subtle_Color.Tangerine.700}" } }, "color-4": { @@ -560,6 +572,10 @@ "thick-3": { "$type": "color", "$value": "{Subtle_Color.Mango.600}" + }, + "thick-4": { + "$type": "color", + "$value": "{Subtle_Color.Mango.700}" } }, "color-5": { @@ -586,6 +602,10 @@ "thick-3": { "$type": "color", "$value": "{Subtle_Color.Lemon.600}" + }, + "thick-4": { + "$type": "color", + "$value": "{Subtle_Color.Lemon.700}" } }, "color-6": { @@ -612,6 +632,10 @@ "thick-3": { "$type": "color", "$value": "{Subtle_Color.Olive.600}" + }, + "thick-4": { + "$type": "color", + "$value": "{Subtle_Color.Olive.700}" } }, "color-7": { @@ -638,6 +662,10 @@ "thick-3": { "$type": "color", "$value": "{Subtle_Color.Lime.600}" + }, + "thick-4": { + "$type": "color", + "$value": "{Subtle_Color.Lime.700}" } }, "color-8": { @@ -664,6 +692,10 @@ "thick-3": { "$type": "color", "$value": "{Subtle_Color.Grass.600}" + }, + "thick-4": { + "$type": "color", + "$value": "{Subtle_Color.Grass.700}" } }, "color-9": { @@ -690,6 +722,10 @@ "thick-3": { "$type": "color", "$value": "{Subtle_Color.Forest.600}" + }, + "thick-4": { + "$type": "color", + "$value": "{Subtle_Color.Forest.700}" } }, "color-10": { @@ -716,6 +752,10 @@ "thick-3": { "$type": "color", "$value": "{Subtle_Color.Jade.600}" + }, + "thick-4": { + "$type": "color", + "$value": "{Subtle_Color.Jade.700}" } }, "color-11": { @@ -742,6 +782,10 @@ "thick-3": { "$type": "color", "$value": "{Subtle_Color.Aqua.600}" + }, + "thick-4": { + "$type": "color", + "$value": "{Subtle_Color.Aqua.700}" } }, "color-12": { @@ -979,6 +1023,172 @@ } } }, + "Palette": { + "text": { + "color-1": { + "$type": "color", + "$value": "{Subtle_Color.Rose.500}" + }, + "color-2": { + "$type": "color", + "$value": "{Subtle_Color.Papaya.500}" + }, + "color-3": { + "$type": "color", + "$value": "{Subtle_Color.Tangerine.500}" + }, + "color-4": { + "$type": "color", + "$value": "{Subtle_Color.Mango.500}" + }, + "color-5": { + "$type": "color", + "$value": "{Subtle_Color.Lemon.500}" + }, + "color-6": { + "$type": "color", + "$value": "{Subtle_Color.Olive.500}" + }, + "color-7": { + "$type": "color", + "$value": "{Subtle_Color.Lime.500}" + }, + "color-8": { + "$type": "color", + "$value": "{Subtle_Color.Grass.500}" + }, + "color-9": { + "$type": "color", + "$value": "{Subtle_Color.Forest.500}" + }, + "color-10": { + "$type": "color", + "$value": "{Subtle_Color.Jade.500}" + }, + "color-11": { + "$type": "color", + "$value": "{Subtle_Color.Aqua.500}" + }, + "color-12": { + "$type": "color", + "$value": "{Subtle_Color.Azure.500}" + }, + "color-13": { + "$type": "color", + "$value": "{Subtle_Color.Denim.500}" + }, + "color-14": { + "$type": "color", + "$value": "{Subtle_Color.Mauve.500}" + }, + "color-15": { + "$type": "color", + "$value": "{Subtle_Color.Lavender.500}" + }, + "color-16": { + "$type": "color", + "$value": "{Subtle_Color.Lilac.500}" + }, + "color-17": { + "$type": "color", + "$value": "{Subtle_Color.Mallow.500}" + }, + "color-18": { + "$type": "color", + "$value": "{Subtle_Color.Camellia.500}" + }, + "color-19": { + "$type": "color", + "$value": "{Subtle_Color.Smoke.500}" + }, + "color-20": { + "$type": "color", + "$value": "{Subtle_Color.Iron.500}" + } + }, + "bg": { + "color-1": { + "$type": "color", + "$value": "{Subtle_Color.Rose.300}" + }, + "color-2": { + "$type": "color", + "$value": "{Subtle_Color.Papaya.300}" + }, + "color-3": { + "$type": "color", + "$value": "{Subtle_Color.Tangerine.300}" + }, + "color-4": { + "$type": "color", + "$value": "{Subtle_Color.Mango.300}" + }, + "color-5": { + "$type": "color", + "$value": "{Subtle_Color.Lemon.300}" + }, + "color-6": { + "$type": "color", + "$value": "{Subtle_Color.Olive.300}" + }, + "color-7": { + "$type": "color", + "$value": "{Subtle_Color.Lime.300}" + }, + "color-8": { + "$type": "color", + "$value": "{Subtle_Color.Grass.300}" + }, + "color-9": { + "$type": "color", + "$value": "{Subtle_Color.Forest.300}" + }, + "color-10": { + "$type": "color", + "$value": "{Subtle_Color.Jade.300}" + }, + "color-11": { + "$type": "color", + "$value": "{Subtle_Color.Aqua.300}" + }, + "color-12": { + "$type": "color", + "$value": "{Subtle_Color.Azure.300}" + }, + "color-13": { + "$type": "color", + "$value": "{Subtle_Color.Denim.300}" + }, + "color-14": { + "$type": "color", + "$value": "{Subtle_Color.Mauve.300}" + }, + "color-15": { + "$type": "color", + "$value": "{Subtle_Color.Lavender.300}" + }, + "color-16": { + "$type": "color", + "$value": "{Subtle_Color.Lilac.300}" + }, + "color-17": { + "$type": "color", + "$value": "{Subtle_Color.Mallow.300}" + }, + "color-18": { + "$type": "color", + "$value": "{Subtle_Color.Camellia.300}" + }, + "color-19": { + "$type": "color", + "$value": "{Subtle_Color.Smoke.300}" + }, + "color-20": { + "$type": "color", + "$value": "{Subtle_Color.Iron.300}" + } + } + }, "Brand": { "Skyline": { "$type": "color", diff --git a/src/@types/translations/en.json b/src/@types/translations/en.json index c0b0c4f7..948ceda4 100644 --- a/src/@types/translations/en.json +++ b/src/@types/translations/en.json @@ -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", diff --git a/src/application/slate-yjs/types.ts b/src/application/slate-yjs/types.ts index 9d695891..4c251d57 100644 --- a/src/application/slate-yjs/types.ts +++ b/src/application/slate-yjs/types.ts @@ -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', } - diff --git a/src/assets/icons/text_color.svg b/src/assets/icons/text_color.svg index 123da8c0..e6e94c33 100644 --- a/src/assets/icons/text_color.svg +++ b/src/assets/icons/text_color.svg @@ -1,4 +1,4 @@ - - + + diff --git a/src/assets/icons/text_highlight.svg b/src/assets/icons/text_highlight.svg index 2c2cb2ca..f370b333 100644 --- a/src/assets/icons/text_highlight.svg +++ b/src/assets/icons/text_highlight.svg @@ -1,4 +1,4 @@ - - + + diff --git a/src/components/_shared/color-picker/ColorTile.tsx b/src/components/_shared/color-picker/ColorTile.tsx new file mode 100644 index 00000000..fb7fd786 --- /dev/null +++ b/src/components/_shared/color-picker/ColorTile.tsx @@ -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 ( + + A + + ); + } + + return ( +
+ ); + }, [active, isText, value]); + + return ( +
+ {child} +
+ ); +}); diff --git a/src/components/_shared/color-picker/CustomColorPicker.tsx b/src/components/_shared/color-picker/CustomColorPicker.tsx new file mode 100644 index 00000000..cbc4fb8a --- /dev/null +++ b/src/components/_shared/color-picker/CustomColorPicker.tsx @@ -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 ( +
+ +
+
+ setColorHex(e.target.value)} + onBlur={(e) => handleChange(e.currentTarget.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + (e.target as HTMLInputElement).blur(); + } + }} + /> +
+
+ + +
+
+ ); +} + +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)}`; +} diff --git a/src/components/_shared/color-picker/index.ts b/src/components/_shared/color-picker/index.ts index e305fd3f..7abf0884 100644 --- a/src/components/_shared/color-picker/index.ts +++ b/src/components/_shared/color-picker/index.ts @@ -1 +1,2 @@ -export * from './ColorPicker'; \ No newline at end of file +export * from './ColorPicker'; +export * from './ColorTile'; diff --git a/src/components/_shared/color-picker/style.css b/src/components/_shared/color-picker/style.css new file mode 100644 index 00000000..c918072c --- /dev/null +++ b/src/components/_shared/color-picker/style.css @@ -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%); +} diff --git a/src/components/editor/EditorContext.tsx b/src/components/editor/EditorContext.tsx index f87a10b2..af2e9deb 100644 --- a/src/components/editor/EditorContext.tsx +++ b/src/components/editor/EditorContext.tsx @@ -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) | undefined; eventEmitter?: EventEmitter; getMentionUser?: (uuid: string) => Promise; } diff --git a/src/components/editor/components/leaf/Leaf.tsx b/src/components/editor/components/leaf/Leaf.tsx index 459e6e59..030f6a6c 100644 --- a/src/components/editor/components/leaf/Leaf.tsx +++ b/src/components/editor/components/leaf/Leaf.tsx @@ -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 = {newChildren}; - } - if (leaf.underline) { newChildren = {newChildren}; } @@ -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 = ( + + {newChildren} + + ); + } + if (leaf.href) { newChildren = ( - + {newChildren} ); diff --git a/src/components/editor/components/leaf/href/Href.tsx b/src/components/editor/components/leaf/href/Href.tsx index 67a6048a..89b12ca1 100644 --- a/src/components/editor/components/leaf/href/Href.tsx +++ b/src/components/editor/components/leaf/href/Href.tsx @@ -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 && ( diff --git a/src/components/editor/components/toolbar/selection-toolbar/SelectionToolbar.hooks.ts b/src/components/editor/components/toolbar/selection-toolbar/SelectionToolbar.hooks.ts index 1074b0b7..4cc82ca2 100644 --- a/src/components/editor/components/toolbar/selection-toolbar/SelectionToolbar.hooks.ts +++ b/src/components/editor/components/toolbar/selection-toolbar/SelectionToolbar.hooks.ts @@ -66,9 +66,7 @@ export function useVisible() { }, [visible, removeDecorate]); useEffect(() => { - const handleMouseDown = () => { - const { selection } = editor; if(selection && Range.isExpanded(selection)) { diff --git a/src/components/editor/components/toolbar/selection-toolbar/ToolbarActions.tsx b/src/components/editor/components/toolbar/selection-toolbar/ToolbarActions.tsx index 4637cef8..16f1116a 100644 --- a/src/components/editor/components/toolbar/selection-toolbar/ToolbarActions.tsx +++ b/src/components/editor/components/toolbar/selection-toolbar/ToolbarActions.tsx @@ -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(null); + const disableFocusRef = useRef(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 && } - + +
); } diff --git a/src/components/editor/components/toolbar/selection-toolbar/actions/BgColor.tsx b/src/components/editor/components/toolbar/selection-toolbar/actions/BgColor.tsx new file mode 100644 index 00000000..cd53072c --- /dev/null +++ b/src/components/editor/components/toolbar/selection-toolbar/actions/BgColor.tsx @@ -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([]); + const [customColors, setCustomColors] = useState([]); + const recentColorToSave = useRef(null); + const initialColor = useRef(null); + + const [activeSubscriptionPlan, setActiveSubscriptionPlan] = useState(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 ( +
+ setIsChoosingCustom(false)} + onApply={(color) => { + handleCreateCustomColor(color); + handlePickColor(color); + setIsChoosingCustom(false); + }} + /> +
+ ); + } + + return ( +
+ {recentColors.length > 0 && ( + <> +
{t('colors.recent')}
+
+ {recentColors.map((color, index) => ( + handlePickColor(color)} + /> + ))} +
+ + + )} +
{t('editor.backgroundColor')}
+
+ {builtinColors.map((color, index) => ( + + {color.label} + + handlePickColor(color.color)} + /> + + + ))} +
+ +
{t('colors.custom')}
+
+ {customColors.map((color, index) => ( + handlePickColor(color)} + /> + ))} + setIsChoosingCustom(true)} /> +
+
+ ); + }, [ + builtinColors, + customColors, + getRawColorValue, + handleCreateCustomColor, + handlePickColor, + isChoosingCustom, + recentColors, + singleColor, + t, + ]); + + return ( + { + if (!open) { + saveRecentColors(); + focusEditor(50); + } + + setIsOpen(open); + }} + > + + { + e.stopPropagation(); + e.preventDefault(); + + forceShow(!isOpen); + if (isOpen) { + saveRecentColors(); + focusEditor(); + } else { + handleOpen(); + toggleDisableEditorFocus(); + } + + setIsOpen(!isOpen); + }} + > + + + + 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} + + + ); +} + +export default BgColor; diff --git a/src/components/editor/components/toolbar/selection-toolbar/actions/Color.tsx b/src/components/editor/components/toolbar/selection-toolbar/actions/Color.tsx deleted file mode 100644 index 7e1041b8..00000000 --- a/src/components/editor/components/toolbar/selection-toolbar/actions/Color.tsx +++ /dev/null @@ -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(null); - const open = Boolean(anchorEl); - - useEffect(() => { - if (!toolbarVisible) { - setAnchorEl(null); - } - }, [toolbarVisible]); - - const onClick = useCallback((e: React.MouseEvent) => { - 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 ( -
-
-
{t('editor.textColor')}
-
- {editorTextColors.map((color, index) => { - return ( - -
handlePickedColor(EditorMarkFormat.FontColor, color.color)} - style={{ - color: color.color || 'var(--text-primary)', - }} - > -
- -
- - ); - })} -
-
-
-
{t('editor.backgroundColor')}
-
- {editorBgColors.map((color, index) => { - return ( - -
handlePickedColor(EditorMarkFormat.BgColor, color.color)} - > -
-
-
- - ); - })} -
-
-
- ); - }, [activeBgColor, activeFontColor, editorBgColors, editorTextColors, handlePickedColor, t]); - - return ( - <> - - - - {toolbarVisible && ( - { - 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} - - )} - - ); -} - -export default Color; diff --git a/src/components/editor/components/toolbar/selection-toolbar/actions/TextColor.tsx b/src/components/editor/components/toolbar/selection-toolbar/actions/TextColor.tsx new file mode 100644 index 00000000..f4abb520 --- /dev/null +++ b/src/components/editor/components/toolbar/selection-toolbar/actions/TextColor.tsx @@ -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([]); + const [customColors, setCustomColors] = useState([]); + const recentColorToSave = useRef(null); + const initialColor = useRef(null); + + const [activeSubscriptionPlan, setActiveSubscriptionPlan] = useState(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 ( +
+ setIsChoosingCustom(false)} + onApply={(color) => { + handleCreateCustomColor(color); + handlePickColor(color); + setIsChoosingCustom(false); + }} + /> +
+ ); + } + + return ( +
+ {recentColors.length > 0 && ( + <> +
{t('colors.recent')}
+
+ {recentColors.map((color, index) => ( + handlePickColor(color)} + /> + ))} +
+ + + )} +
{t('editor.textColor')}
+
+ {builtinColors.map((color, index) => ( + + {color.label} + + handlePickColor(color.color)} + /> + + + ))} +
+ +
{t('colors.custom')}
+
+ {customColors.map((color, index) => ( + handlePickColor(color)} + /> + ))} + setIsChoosingCustom(true)} /> +
+
+ ); + }, [ + builtinColors, + customColors, + getRawColorValue, + handleCreateCustomColor, + handlePickColor, + isChoosingCustom, + recentColors, + singleColor, + t, + ]); + + return ( + { + if (!open) { + saveRecentColors(); + focusEditor(50); + } + + setIsOpen(open); + }} + > + + { + e.stopPropagation(); + e.preventDefault(); + + forceShow(!isOpen); + if (isOpen) { + saveRecentColors(); + focusEditor(0); + } else { + handleOpen(); + toggleDisableEditorFocus(); + } + + setIsOpen(!isOpen); + }} + > + + + + 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} + + + ); +} + +export default TextColor; + +export function CreateCustomColorTile({ onClick }: { onClick?: () => void }) { + return ( +
+ +
+ ); +} diff --git a/src/components/view-meta/CoverColors.tsx b/src/components/view-meta/CoverColors.tsx index 209ef075..f8f5e0db 100644 --- a/src/components/view-meta/CoverColors.tsx +++ b/src/components/view-meta/CoverColors.tsx @@ -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 ( diff --git a/src/slate-editor.d.ts b/src/slate-editor.d.ts index f621d81c..ce774de6 100644 --- a/src/slate-editor.d.ts +++ b/src/slate-editor.d.ts @@ -23,6 +23,8 @@ interface EditorInlineAttributes { reminder_id?: string; reminder_option?: string; }; + af_text_color?: string; + af_background_color?: string; } type CustomElement = { diff --git a/src/styles/variables/semantic.dark.css b/src/styles/variables/semantic.dark.css index 6da2a720..083fa30e 100644 --- a/src/styles/variables/semantic.dark.css +++ b/src/styles/variables/semantic.dark.css @@ -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; diff --git a/src/styles/variables/semantic.light.css b/src/styles/variables/semantic.light.css index ff9f1156..935ebf34 100644 --- a/src/styles/variables/semantic.light.css +++ b/src/styles/variables/semantic.light.css @@ -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; diff --git a/src/utils/color.ts b/src/utils/color.ts index 34679a8a..dc610226 100644 --- a/src/utils/color.ts +++ b/src/utils/color.ts @@ -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; } diff --git a/tailwind/new-colors.cjs b/tailwind/new-colors.cjs index 0504adde..d08c1d2f 100644 --- a/tailwind/new-colors.cjs +++ b/tailwind/new-colors.cjs @@ -3,7 +3,7 @@ * AUTO-GENERATED FILE - DO NOT EDIT DIRECTLY * * This file is auto-generated by convert-tokens.cjs script - * Generation time: 2025-06-24T06:01:38.065Z + * Generation time: 2025-07-30T06:18:37.483Z * * To modify these colors, edit the source JSON files and run the token conversion script: * node scripts/system-token/convert-tokens.cjs @@ -146,66 +146,77 @@ module.exports = { "color-1-thick-1": "var(--badge-color-1-thick-1)", "color-1-thick-2": "var(--badge-color-1-thick-2)", "color-1-thick-3": "var(--badge-color-1-thick-3)", + "color-1-thick-4": "var(--badge-color-1-thick-4)", "color-2-light-1": "var(--badge-color-2-light-1)", "color-2-light-2": "var(--badge-color-2-light-2)", "color-2-light-3": "var(--badge-color-2-light-3)", "color-2-thick-1": "var(--badge-color-2-thick-1)", "color-2-thick-2": "var(--badge-color-2-thick-2)", "color-2-thick-3": "var(--badge-color-2-thick-3)", + "color-2-thick-4": "var(--badge-color-2-thick-4)", "color-3-light-1": "var(--badge-color-3-light-1)", "color-3-light-2": "var(--badge-color-3-light-2)", "color-3-light-3": "var(--badge-color-3-light-3)", "color-3-thick-1": "var(--badge-color-3-thick-1)", "color-3-thick-2": "var(--badge-color-3-thick-2)", "color-3-thick-3": "var(--badge-color-3-thick-3)", + "color-3-thick-4": "var(--badge-color-3-thick-4)", "color-4-light-1": "var(--badge-color-4-light-1)", "color-4-light-2": "var(--badge-color-4-light-2)", "color-4-light-3": "var(--badge-color-4-light-3)", "color-4-thick-1": "var(--badge-color-4-thick-1)", "color-4-thick-2": "var(--badge-color-4-thick-2)", "color-4-thick-3": "var(--badge-color-4-thick-3)", + "color-4-thick-4": "var(--badge-color-4-thick-4)", "color-5-light-1": "var(--badge-color-5-light-1)", "color-5-light-2": "var(--badge-color-5-light-2)", "color-5-light-3": "var(--badge-color-5-light-3)", "color-5-thick-1": "var(--badge-color-5-thick-1)", "color-5-thick-2": "var(--badge-color-5-thick-2)", "color-5-thick-3": "var(--badge-color-5-thick-3)", + "color-5-thick-4": "var(--badge-color-5-thick-4)", "color-6-light-1": "var(--badge-color-6-light-1)", "color-6-light-2": "var(--badge-color-6-light-2)", "color-6-light-3": "var(--badge-color-6-light-3)", "color-6-thick-1": "var(--badge-color-6-thick-1)", "color-6-thick-2": "var(--badge-color-6-thick-2)", "color-6-thick-3": "var(--badge-color-6-thick-3)", + "color-6-thick-4": "var(--badge-color-6-thick-4)", "color-7-light-1": "var(--badge-color-7-light-1)", "color-7-light-2": "var(--badge-color-7-light-2)", "color-7-light-3": "var(--badge-color-7-light-3)", "color-7-thick-1": "var(--badge-color-7-thick-1)", "color-7-thick-2": "var(--badge-color-7-thick-2)", "color-7-thick-3": "var(--badge-color-7-thick-3)", + "color-7-thick-4": "var(--badge-color-7-thick-4)", "color-8-light-1": "var(--badge-color-8-light-1)", "color-8-light-2": "var(--badge-color-8-light-2)", "color-8-light-3": "var(--badge-color-8-light-3)", "color-8-thick-1": "var(--badge-color-8-thick-1)", "color-8-thick-2": "var(--badge-color-8-thick-2)", "color-8-thick-3": "var(--badge-color-8-thick-3)", + "color-8-thick-4": "var(--badge-color-8-thick-4)", "color-9-light-1": "var(--badge-color-9-light-1)", "color-9-light-2": "var(--badge-color-9-light-2)", "color-9-light-3": "var(--badge-color-9-light-3)", "color-9-thick-1": "var(--badge-color-9-thick-1)", "color-9-thick-2": "var(--badge-color-9-thick-2)", "color-9-thick-3": "var(--badge-color-9-thick-3)", + "color-9-thick-4": "var(--badge-color-9-thick-4)", "color-10-light-1": "var(--badge-color-10-light-1)", "color-10-light-2": "var(--badge-color-10-light-2)", "color-10-light-3": "var(--badge-color-10-light-3)", "color-10-thick-1": "var(--badge-color-10-thick-1)", "color-10-thick-2": "var(--badge-color-10-thick-2)", "color-10-thick-3": "var(--badge-color-10-thick-3)", + "color-10-thick-4": "var(--badge-color-10-thick-4)", "color-11-light-1": "var(--badge-color-11-light-1)", "color-11-light-2": "var(--badge-color-11-light-2)", "color-11-light-3": "var(--badge-color-11-light-3)", "color-11-thick-1": "var(--badge-color-11-thick-1)", "color-11-thick-2": "var(--badge-color-11-thick-2)", "color-11-thick-3": "var(--badge-color-11-thick-3)", + "color-11-thick-4": "var(--badge-color-11-thick-4)", "color-12-light-1": "var(--badge-color-12-light-1)", "color-12-light-2": "var(--badge-color-12-light-2)", "color-12-light-3": "var(--badge-color-12-light-3)", @@ -261,6 +272,48 @@ module.exports = { "color-20-thick-2": "var(--badge-color-20-thick-2)", "color-20-thick-3": "var(--badge-color-20-thick-3)" }, + "palette": { + "text-color-1": "var(--palette-text-color-1)", + "text-color-2": "var(--palette-text-color-2)", + "text-color-3": "var(--palette-text-color-3)", + "text-color-4": "var(--palette-text-color-4)", + "text-color-5": "var(--palette-text-color-5)", + "text-color-6": "var(--palette-text-color-6)", + "text-color-7": "var(--palette-text-color-7)", + "text-color-8": "var(--palette-text-color-8)", + "text-color-9": "var(--palette-text-color-9)", + "text-color-10": "var(--palette-text-color-10)", + "text-color-11": "var(--palette-text-color-11)", + "text-color-12": "var(--palette-text-color-12)", + "text-color-13": "var(--palette-text-color-13)", + "text-color-14": "var(--palette-text-color-14)", + "text-color-15": "var(--palette-text-color-15)", + "text-color-16": "var(--palette-text-color-16)", + "text-color-17": "var(--palette-text-color-17)", + "text-color-18": "var(--palette-text-color-18)", + "text-color-19": "var(--palette-text-color-19)", + "text-color-20": "var(--palette-text-color-20)", + "bg-color-1": "var(--palette-bg-color-1)", + "bg-color-2": "var(--palette-bg-color-2)", + "bg-color-3": "var(--palette-bg-color-3)", + "bg-color-4": "var(--palette-bg-color-4)", + "bg-color-5": "var(--palette-bg-color-5)", + "bg-color-6": "var(--palette-bg-color-6)", + "bg-color-7": "var(--palette-bg-color-7)", + "bg-color-8": "var(--palette-bg-color-8)", + "bg-color-9": "var(--palette-bg-color-9)", + "bg-color-10": "var(--palette-bg-color-10)", + "bg-color-11": "var(--palette-bg-color-11)", + "bg-color-12": "var(--palette-bg-color-12)", + "bg-color-13": "var(--palette-bg-color-13)", + "bg-color-14": "var(--palette-bg-color-14)", + "bg-color-15": "var(--palette-bg-color-15)", + "bg-color-16": "var(--palette-bg-color-16)", + "bg-color-17": "var(--palette-bg-color-17)", + "bg-color-18": "var(--palette-bg-color-18)", + "bg-color-19": "var(--palette-bg-color-19)", + "bg-color-20": "var(--palette-bg-color-20)" + }, "brand": { "skyline": "var(--brand-skyline)", "aqua": "var(--brand-aqua)",