#Has anyone added Text Color to the RichText input?

241 messages · Page 1 of 1 (latest)

tropic wolf
#

Wondering if anyone has worked with slatejs and getting some of the features like text color added to the rich text component? Having a hard time understanding the setup as I'm not really a React guy.

tropic wolf
tired loom
#

You mean like a color-picking leaf?

tropic wolf
#

I think so! We want to add text color to the richText editor

tired loom
#

I've done a simple leaf that just applies an attribute to the selected text.. might be a good starting place

#
import { LeafButton } from 'payload/components/rich-text'

import './index.scss'

const baseClass = 'custom-leaf'
const name = 'custom_leaf'

const Button = () => (
    <LeafButton format={name}>
        Custom Button (You might want to pop out the color picker here)
    </LeafButton>
)

const Leaf = ({ attributes, leaf, children }) => {
    if (leaf[name]) {
        return <span {...attributes} className={baseClass}>{children}</span>
    }
    return <span {...attributes}>{children}</span>
}

export default {
    name,
    Button,
    Leaf,
}```
#

from there you could probably hook into a color picker component to add a style prop instead of a className to do a direct adjustment of the color value

#

just hypothetical of course :). I had a hard time making just a custom leaf in the first place so maybe this'll get you closer.

tropic wolf
#

Thanks @tired loom 😄

tropic wolf
#

@tired loom I'm bad at react so this was a challenge

#

Well it is a challenge, but I'm getting closer?

#
import React from 'react';
import { LeafButton } from 'payload/components/rich-text'


let spanStyle = {
    color: '#000000'
}

const onChange = (event) => {
    spanStyle.color = event.target.value
}

const Color = ({ attributes, children }) => (
    <span style={spanStyle} {...attributes}>{children}</span>
  );

const color = {
  Button: () => (
    <LeafButton format="color">
      <input onChange={onChange} type="color"></input>
    </LeafButton>
  ),
  Leaf: Color,
};

export default color;
#

not sure why the style isn't applied

tired loom
#

your color export doesn't appear to have a name property. I think that's necessary for one

tropic wolf
#

ooooo

tired loom
#

you might also want to include the if (leaf[name]) { stuff.. I never verified if that's necessary though.

tropic wolf
#

ahhh

tired loom
#

also, I didn't know there was a 'color' input type till today, lol. TIL

#

I think your onChange event makes sense, but where you're probably also losing the logical flow is when you need that spanStyle to update when it changes

#

but.. maybe that's where the leaf property comes in

#

when it changes, the output adjusts to when a leaf is applied vs without in my example. so maybe that's what's missing for you as well

tropic wolf
#

hmmmm

#

so currently I have

#
import React from 'react';
import { LeafButton } from 'payload/components/rich-text'

const baseClass = 'custom-leaf'
const name = 'color'

let spanStyle = {
    color: '#000000'
}

const onChange = (event) => {
    spanStyle.color = event.target.value
}

const Color = ({ attributes, leaf, children }) => {
  if (leaf[name]) {
    return <span style={spanStyle} {...attributes} className={baseClass}>{children}</span>
  }
  return <span style={spanStyle} {...attributes}>{children}</span>
}

const color = {
  Button: () => (
    <LeafButton format="color">
      <input onChange={onChange} type="color"></input>
    </LeafButton>
  ),
  Leaf: Color,
};

export default color;
#

You're saying I should do something with the leaf prop?

tired loom
#

yeah, I think what you have there looks right

#

you might not need the className

#

but in the default export, you'll want a property of name: 'color'

#

you might also remove the style attribute on the non-leaf condition

tropic wolf
#

like..

#
export default {
  color,
  name: 'color'
}
#

?

tired loom
#

nah, in your original color object definition there

#

above Button, probably

tropic wolf
#

I think I already added that no?

#

Not sure how to export name: 'color' from the color obj

tired loom
#
  name: 'color',
  Button: () => (
    <LeafButton format="color">
      <input onChange={onChange} type="color"></input>
    </LeafButton>
  ),
  Leaf: Color,
};```
#

like that

tropic wolf
#

ohhhhh

#

(its early)

tired loom
#

all good lol

tropic wolf
#

Hmmm

#

So it highlights the button when i hover over text i tried to apply color to

#

which is good

#

but no luck yet getting the style applied

tired loom
#

nice nice, progress!

tropic wolf
#

@sacred pasture I saw you did a markdown plugin recently, maybe you can help identify why the span doesn't receive any style?

tropic wolf
#

@vestal jungle If you get a moment today, I'm super confused on how to implement that color input, I think it's pretty close.

#

I wish I knew React

#

maybe it's time to bit the bullet

tropic wolf
#

Is anyone available to help out with this one?

#

Still stuck on adding the leaf

tired loom
#

Lemme try it out myself for a couple minutes here

mystic panther
#

these types of threads make me happy

#

it is very cool to see the collaboration happening!

tropic wolf
#

TY @tired loom

#

I'll also try to learn some React so I can be more self suficient

tired loom
#
import { LeafButton } from 'payload/components/rich-text'

const name = 'color_picker'

let color = '#000000'

const onChange = (event) => {
    console.log('color value changed', event.target.value)
    color = event.target.value
}

const Button = () => (
    <LeafButton format={name}>
        <input onChange={onChange} type="color"></input>
    </LeafButton>
)

const Leaf = ({ attributes, leaf, children }) => {
    if (leaf[name]) {
        return <span style={{color}} {...attributes}>{children}</span>
    }
    return <span {...attributes}>{children}</span>
}

export default {
    name,
    Button,
    Leaf,
}```
#

this gets us closer, but the updating of the color is lagged

#

like if you set the color, then click around a bit, it eventually updates

#

maybe there's something in the docs that helps explain..

tropic wolf
#

hmmmm

#

Interesting because I don't see the color change

tired loom
#

seems to update the state of the text when you click the leaf again

#

hm, the fundamental issue here appears to be that leaves can only carry around a boolean value

#

i.e. whether or not selected text has a given style applied or not

#

so even if we got it to work in the editor, it's only going to provide true/false anyway

#

at least with how we currently have it

#

I'll try it real quick here... the I gotta change gears for the time being

tropic wolf
#

Thank you @tired loom

#

Right that was my first though

#

thought

#

However, just importing that plugin into the collection file

#

And adding it to the plugins array in my leaf config, caused an error

tired loom
#
import { LeafButton } from 'payload/components/rich-text'
import { ColorPlugin, ColorButton, ColorStateModel, ColorMark } from '@slate-editor/color-plugin'

const name = 'color_picker'
const colorPluginOptions = new ColorStateModel().rgba({ r: 100, g: 100, b: 100, a: 1 }).gen()


const Button = () => {
    return (
        <ColorButton format={name}>
            initialState={colorPluginOptions}
            pickerDefaultPosition={{ x: -520, y: 17 }}
        </ColorButton>
    )
}

const Leaf = ({ attributes, leaf, children }) => {
    if (leaf[name]) {
        console.log(leaf)
        return <span {...attributes}>{children}</span>
    }
    return <span {...attributes}>{children}</span>
}

const plugins = [
    ColorPlugin()
]

export default {
    name,
    Button,
    Leaf,
    plugins,
}```
#

the main thing I haven't got working here is how to get the "leaf" portion working

#

my guess is it has to do with the "ColorMark" from the color-plugin since its code resembles that of a leaf/element

#

Hmm... yeah I gotta change gears. But part of the issue atm is not knowing how to get the plugin to work through payload's passthru to slate.js

tropic wolf
#

hmmm

tired loom
#

the color picker example doesn't show a definition of a leaf or element. i'm guessing because a 'ColorMark' is used instead through the plugin definition

#

but if you don't supply a leaf or element, it seems payload complains

#

ok... last try this time lol ```import React from 'react'
import { LeafButton } from 'payload/components/rich-text'
import { ColorPlugin, ColorButton, ColorStateModel, ColorMark } from '@slate-editor/color-plugin'

const name = 'color_picker'
const colorPluginOptions = new ColorStateModel().rgba({ r: 100, g: 100, b: 100, a: 1 }).gen()

const Button = () => {
return (
<ColorButton format={name}>
initialState={colorPluginOptions}
pickerDefaultPosition={{ x: -520, y: 17 }}
</ColorButton>
)
}

const Leaf = ({ attributes, leaf, children }) => {
if (leaf[name]) {
return <ColorMark {...attributes}>{children}</ColorMark>
}
return <span {...attributes}>{children}</span>
}

const plugins = [
ColorPlugin()
]

export default {
name,
Button,
Leaf,
plugins,
}

#

I noticed ColorMark returns a react element so.. this might be closer

#

but it errors due to an undefined object

#

makes me wonder if this plugin is just too old and out of date with slate perhaps

#

might want to just start up a clean project with just slate.js and try to use this plugin in it

#

could reveal some things we need to do in the payload context

#

good luck for now!

tropic wolf
#

Will do TY so much!

tropic wolf
#

Got the color applied

#

But still bad at React

#

lmao

#

@tired loom progress

#
import React from "react";
import { LeafButton } from "payload/components/rich-text";
import {Editor} from 'slate'
import { useSlate } from 'slate-react';
const name = "color_picker";

let color = "#000000";

const isLeafActive = (editor) => {
  const leaves = Editor.marks(editor);
  return leaves ? leaves[name] === true : false;
};


const onChange = (event) => {
  color = event.target.value;
};

const Button = ({format, children}) => {
  const editor = useSlate()
  const active = isLeafActive(editor)
  if (active) {
    // Set input color to leaf color somehow
  } else {
    // Set input color to default somehow
  }
  return <div><input onChange={onChange} type="color"></input><LeafButton format={name} >
    {active ? 'Remove Color' : 'Apply Color'}
  </LeafButton></div>
};

const Leaf = ({ attributes, leaf, children }) => {

  if (leaf[name]) {
    return (
      <span       style={{
        color
      }} {...attributes}>
        {children}
      </span>
    );
  }
  return <span {...attributes}>{children}</span>;
};

export default {
  name,
  Button,
  Leaf,
};
#

So it applies the color, and recognizes when the leaf is active

#

But not sure how to remove the style if the button is pressed while it is active

#

Also not sure how to set the input to the leaf color when leaf is active

tired loom
#

can you confirm that the data of the color you set is coming out in the api too? i.e. if you request this data, will the color be there to use

tropic wolf
#

Hmmm

#

Working on that

#

tried doing like

#

leaf[name] = color

#

and also leaf.color

#

in case it allowed other props

#

neither save that on the item

#

only color_picker=true

tired loom
#

Yeah, that detail is what eludes me. My guess is the color picker plugin might reveal how that is done if you look at the source

tropic wolf
#

i thought "attributes" was something to store data

#

but yeah, no leads

tropic wolf
#

:*(

vestal jungle
#

HELLO

#

i am alive

#

trying to clarify a few things quick

#
  1. the color picker plugin is a custom field, right? so there should likely be little overlap besides the actual JS to open a color picker dialog
  2. attributes in Slate are not where you'd want to store a color. That is an internal Slate convention. Instead, you store the "state" of a Slate leaf / element directly on the node itself as additional properties
#

the best way to build this IMO is to look at a simple custom leaf, like here:

#

there are only a few small things that need to be modified from this example:

#
  1. the Button component should be modified to allow the user to pick a color, and then from there, apply it to the node, rather than simply toggling the property on or off using toggleLeaf. So instead of re-using the built-in LeafButton, you need to make your own, and customize the logic that is contained within there
  2. Instead of just storing a boolean true / false on the leaf itself, you would store the hex value of the color that was selected by the user
#

then boom done

tropic wolf
#

Thanks so much for the info @vestal jungle

#

Ill try to build this out now!

tired loom
#

haven't lost sight of this btw. I'm going to give it a try as well when I finish my work here

tropic wolf
#

Woot!

#

yeah I didn't make more progress with the example provided

#

It's still not clear how we save the color on the leaf object

tropic wolf
#

@vestal jungle I think I'm most confused on the you would store the hex value of the color that was selected by the user

#

I did try to set the value of color_picker (name) to the hex

#

But it did not seem to save it

vestal jungle
#

here is a basic rich text document with 2 "leaves" - the first one has no "marks", but the second one has a bold "mark"

#

notice that the actual "state" of bold is stored directly on the leaf as a boolean

#

rather than storing a boolean, you would store your hex value of the color that the user chose directly on the leaf next to bold

tropic wolf
#

I think I did try that, like this?

#
const Leaf = ({ attributes, leaf, children }) => {
  if (leaf[name]) {
    leaf[name] = color
    return (
      <span       style={{
        color
      }} {...attributes}>
        {children}
      </span>
    );
  }
  return <span {...attributes}>{children}</span>;
};
vestal jungle
#

well, you still need that part, but that is JUST regarding how to render the leaf in the rich text editor itself. you still need to actually store the value on the leaf

tropic wolf
#

hmm

vestal jungle
#

your actual leaf component itself looks good

#

but are you actually storing the prop on the leaf? this would be done in the button, not the leaf component itself

tropic wolf
#

I don't think I am, that's where I'm struggling

#

Totally thought it was in the leaf logic

vestal jungle
#

nope. think of the leaf logic as just simply a way to render the output

#

note that you CAN allow them to re-choose the color from the leaf itself

#

but the first step is actually just using the button to "toggle on" the color

#

then from there, if you wanted to, you could use the leaf component to allow the color to be changed or removed

#

but that is optional

tropic wolf
#

Are there examples of a button saving data?

#

I'm not sure at which part, maybe I should check out that recent markdown example?

vestal jungle
#

yes, that PurpleBackground example above shows that

#

you click the button and it enables the purple background on the leaf

#

but it only stores a boolean. you need to store the hex value

tropic wolf
#

Oooo ok, I think I'm confused on which part is responsible for saving, because

#
const Button = () => (
  <LeafButton format="purple-background">
    Purple Background
  </LeafButton>
);
#

Format seems to set that object prop right?

vestal jungle
#
  1. the Button component should be modified to allow the user to pick a color, and then from there, apply it to the node, rather than simply toggling the property on or off using toggleLeaf. So instead of re-using the built-in LeafButton, you need to make your own, and customize the logic that is contained within there
#

the logic to save / toggle the prop on the leaf is contained within the LeafButton component

#

you need to not use that, and write your own version of that

tropic wolf
#

Ahhhhh

vestal jungle
#

because that component is simple and just toggles a boolean on / off

tropic wolf
#

Ok that's the part I didn't get, I'll review that component

vestal jungle
#

so it's not applicable for your re-use

tropic wolf
vestal jungle
#

that is an element button

#

you are working with a leaf

tropic wolf
#

Dang im striking out today

#

Ok ok, ill find that Leaf Button

vestal jungle
tropic wolf
#

Woot!

#

{ "text": "we are available", "color_picker": "#c02a2a" },

vestal jungle
#

beautiful!!!!!!

#

that is exactly what you need

tropic wolf
#

Yall are the best

#

Cleaning it up and posting example

tired loom
#

@tropic wolf ever get it cleaned up? I'm curious to see it!

tropic wolf
#

AYY

#

I was planning on cleaning it up more today, but this is what I currently have

#

So the current issues are

#

1.) Change the color from the input, select a string, press the apply color button. The text will change color. If you select a different node, and click on the colored node, the button will recognize the leaf, but will not remove the formatting unless the whole string is selected again

#

2.) Click on a leaf should update the state of the color input, I commented out that part as I was stuck on it

#

3.) General styling

#

@tired loom Let me know your thoughts

tired loom
#

I think this great progress! I'll have to give it a try myself at some point here. Great work!

tropic wolf
#

Thanks to everyone for their help!

charred kernel
#

Hi there, I was looking for something that was capable of doing the same as this gist. I have just tried what is done here but is giving me error related to React children and can't find how to solve. Any help?

hasty jacinth
charred kernel
#

Hey @hasty jacinth the code is the same as in the gist and here is how it's being used

#

Then on the UI there's this

#

And as soon as I type this happens

#

What do you think?

tropic wolf
#

@charred kernel were you following the code in my gist?

charred kernel
#

Hi @tropic wolf, yes! I just went there and copy, sorry about that

tropic wolf
#

No problem

#

Here is how to implement it in a collection config

#
import { Block } from "payload/types";
import TestLeaf from "../../editor/TestLeaf";

const SupportModalBlock: Block = {
  slug: "SupportModal",
  fields: [
    {
      name: "modalTitle",
      label: "Support Modal Title",
      type: "text",
      required: true,
    },
    {
      name: "modalContent",
      label: "Support Modal Content",
      type: "richText",
      admin: {
        leaves: [
          {
            name: "color_picker",
            Button: TestLeaf.Button,
            Leaf: TestLeaf.Leaf,
            plugins: [
              // any plugins that are required by this leaf go here
            ],
          },
        ],
      },
    },
    {
      name: "modalPhone",
      label: "Support Phone",
      type: "text",
      required: true,
    },
    {
      name: "modalEmail",
      label: "Support Email",
      type: "text",
      required: true,
    },
    {
      name: "modalFax",
      label: "Support Fax",
      type: "text",
      required: true,
    },
  ],
};

export { SupportModalBlock };

#

Let me know if that works

#

Also note: This picker is a WIP, if you make it better please post here

charred kernel
#

I'm having a look at this lines. It seem that the error comes from here

tropic wolf
#

It

#

It's interesting you're getting an error

#

I think I have the same code without issue

#

let me check my latest

#

thats my current

charred kernel
#

@tropic wolf now I can say that the error is not from your code at all. It seems to be related with the richText field itself since I'm getting the same error even when using it as its simplest implementation.

#

Thank you very much for the help!

shadow lion
#

Hi Guys, i use your solution @tropic wolf and get Error: Uncaught Error: The useSlate hook must be used inside the <Slate> component's context.

what am I doing wrong?

@tropic wolf could you show your package.json file your Payload CMS project ?

tropic wolf
#

Sure

#

@shadow lion Can i check out your collection config?

#

The package.json shouldn't matter at all since we are not adding any new dependencies

#

and payload is at latest

shadow lion
#

WOW I was only able to solve this problem because it needed to install a version dependency: "slate": "0.88.1",
"slate-react": "0.88.0",

tropic wolf
#

oh weird

#

I didn't expect that, does it work now?

shadow lion
#

yeah exactly

shadow lion
#

@tropic wolf sorry for the question, but how to get the attribute color on the consumer (client app) ?

I only get the text in the node.text ... but I don’t understand how to get the color attribute for displaying it ..

tropic wolf
#

@shadow lion it should be saving the color to the lead in the generated json

shadow lion
#

@tropic wolf I got this error ( Error: Uncaught Error: The useSlate hook must be used inside the <Slate> component's context. ) again after a while, maybe the version of 'react-slate' that Payload uses has changed?

tropic wolf
#

@shadow lion Possible, I'm actually not super familiar with the inner workings of Slate and my initial implementation was a learning experiment. @hasty jacinth may have some insight though!

regal lark
#

Hey @tropic wolf , does this custom leaf still work? I used the code from your gist but it doesn't update the color in the richtext and payload also doesn't recognize that it made a change

#

Ah now it works, I didn't know that you have to select the color before you select your text but there is another issue: if I select my text that has colors the color picker pick the color from the text

old portal
#

@tropic wolf Hi, I am getting the following error Object literal may only specify known properties, and 'leaves' does not exist in type 'Admin & { components?: { Error?: ComponentType<Props>; Label?: ComponentType<Props>; }; }

amber quest
#

I've created a plugin to edit text color, highlight and block background, maybe you can check if it cover your needs https://www.npmjs.com/package/payloadcms-lexical-ext

ionic holly
abstract terrace
abstract terrace
#

Added my fix to the comment @amber quest , would like some comments re this

pliant anchor
#

Any solution to add color to richtext?

abstract terrace
#

yes

#

ill forward link to repo

dim bone
dim bone
#

not the inline one

#

you can contribute if you want to

abstract terrace
#

No prob, would love to contribute 😄

undone cape
#

If you're looking for a walkthrough on how to use the core TextStateFeature, here you go: https://youtu.be/RHBdlP7a-sA

Repo — https://github.com/nlvcodes/nlv_codes_example/tree/20250701-textstatefeature-implementation
Become a paying member of the channel — https://www.youtube.com/channel/UC8mOjhMBTyPPScCF9vYtwFg/join
Find me on Twitch — https://www.twitch.tv/nlv_codes
Want to stay up to date with recent web dev news? Subscribe to my newsletter — https:...

▶ Play video
worthy fiber