Has anyone added Text Color to the RichText input?

default discord avatar
notchr
8 months ago
221

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.



https://www.npmjs.com/package/@slate-editor/color-plugin
  • default discord avatar
    jakey___
    8 months ago

    You mean like a color-picking leaf?

  • default discord avatar
    notchr
    8 months ago

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

  • default discord avatar
    jakey___
    8 months ago

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



    import React from 'react'
    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.

  • default discord avatar
    notchr
    8 months ago

    Thanks @jakey

    😄

    @jakey

    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

  • default discord avatar
    jakey___
    8 months ago

    your color export doesn't appear to have a

    name

    property. I think that's necessary for one

  • default discord avatar
    notchr
    8 months ago

    ooooo

  • default discord avatar
    jakey___
    8 months ago

    you might also want to include the

    if (leaf[name]) {

    stuff.. I never verified if that's necessary though.

  • default discord avatar
    notchr
    8 months ago

    ahhh

  • default discord avatar
    jakey___
    8 months ago

    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

  • default discord avatar
    notchr
    8 months ago

    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?

  • default discord avatar
    jakey___
    8 months ago

    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

  • default discord avatar
    notchr
    8 months ago

    like..



    export default {
      color,
      name: 'color'
    }


    ?

  • default discord avatar
    jakey___
    8 months ago

    nah, in your original color object definition there



    above Button, probably

  • default discord avatar
    notchr
    8 months ago

    I think I already added that no?



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

  • default discord avatar
    jakey___
    8 months ago
    const color = {
      name: 'color',
      Button: () => (
        <LeafButton format="color">
          <input onChange={onChange} type="color"></input>
        </LeafButton>
      ),
      Leaf: Color,
    };


    like that

  • default discord avatar
    notchr
    8 months ago

    ohhhhh



    (its early)

  • default discord avatar
    jakey___
    8 months ago

    all good lol

  • default discord avatar
    notchr
    8 months ago

    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

    image.png
  • default discord avatar
    jakey___
    8 months ago

    nice nice, progress!

  • default discord avatar
    notchr
    8 months ago

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



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



    Is anyone available to help out with this one?



    Still stuck on adding the leaf

  • default discord avatar
    jakey___
    8 months ago

    Lemme try it out myself for a couple minutes here

  • discord user avatar
    seanzubrickas
    Payload Team
    8 months ago

    these types of threads make me happy



    it is very cool to see the collaboration happening!

  • default discord avatar
    notchr
    8 months ago

    TY @jakey___



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

  • default discord avatar
    jakey___
    8 months ago
    import React from 'react'
    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..

  • default discord avatar
    notchr
    8 months ago

    hmmmm



    Interesting because I don't see the color change

  • default discord avatar
    jakey___
    8 months ago

    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



    might be able to get it working with this though..

    https://www.npmjs.com/package/@slate-editor/color-plugin


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

  • default discord avatar
    notchr
    8 months ago

    Thank you @jakey___



    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

  • default discord avatar
    jakey___
    8 months ago
    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]) {
            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

  • default discord avatar
    notchr
    8 months ago

    hmmm

  • default discord avatar
    jakey___
    8 months ago

    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!

  • default discord avatar
    notchr
    8 months ago

    Will do TY so much!





    Got the color applied



    But still bad at React



    lmao



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

    image.png
  • default discord avatar
    jakey___
    8 months ago

    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

  • default discord avatar
    notchr
    8 months ago

    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

  • default discord avatar
    jakey___
    8 months ago

    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

  • default discord avatar
    notchr
    8 months ago

    i thought "attributes" was something to store data



    but yeah, no leads



    :*(

  • discord user avatar
    jmikrut
    Payload Team
    8 months ago

    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:



    https://github.com/payloadcms/payload/tree/master/test/admin/components/richText/leaves/PurpleBackground


    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

  • default discord avatar
    notchr
    8 months ago

    Thanks so much for the info @jmikrut



    Ill try to build this out now!

  • default discord avatar
    jakey___
    8 months ago

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

  • default discord avatar
    notchr
    8 months ago

    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



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

  • discord user avatar
    jmikrut
    Payload Team
    8 months ago


    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
    image.png
  • default discord avatar
    notchr
    8 months ago

    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>;
    };
  • discord user avatar
    jmikrut
    Payload Team
    8 months ago

    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

  • default discord avatar
    notchr
    8 months ago

    hmm

  • discord user avatar
    jmikrut
    Payload Team
    8 months ago

    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

  • default discord avatar
    notchr
    8 months ago

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



    Totally thought it was in the leaf logic

  • discord user avatar
    jmikrut
    Payload Team
    8 months ago

    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

  • default discord avatar
    notchr
    8 months ago

    Are there examples of a button saving data?



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

  • discord user avatar
    jmikrut
    Payload Team
    8 months ago

    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

  • default discord avatar
    notchr
    8 months ago

    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?

  • discord user avatar
    jmikrut
    Payload Team
    8 months ago
    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

  • default discord avatar
    notchr
    8 months ago

    Ahhhhh

  • discord user avatar
    jmikrut
    Payload Team
    8 months ago

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

  • default discord avatar
    notchr
    8 months ago

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

  • discord user avatar
    jmikrut
    Payload Team
    8 months ago

    so it's not applicable for your re-use

  • discord user avatar
    jmikrut
    Payload Team
    8 months ago

    that is an

    element

    button



    you are working with a

    leaf
  • default discord avatar
    notchr
    8 months ago

    Dang im striking out today



    Ok ok, ill find that Leaf Button

  • default discord avatar
    notchr
    8 months ago

    Woot!



     { "text": "we are available", "color_picker": "#c02a2a" },
  • discord user avatar
    jmikrut
    Payload Team
    8 months ago

    beautiful!!!!!!



    that is

    exactly

    what you need

  • default discord avatar
    notchr
    8 months ago

    Yall are the best



    Cleaning it up and posting example

  • default discord avatar
    jakey___
    8 months ago

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

  • default discord avatar
    notchr
    8 months ago

    AYY



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



    https://gist.github.com/notchris/1d5f2db9a20d84cff61a8c54e05f2e65


    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



    @jakey___ Let me know your thoughts

  • default discord avatar
    jakey___
    8 months ago

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

  • default discord avatar
    notchr
    8 months ago

    Thanks to everyone for their help!

  • default discord avatar
    edsonv
    8 months ago

    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?

  • discord user avatar
    alessiogr
    Payload Team
    8 months ago

    Hey hey! Could you share the error and optimally the code as well?

  • default discord avatar
    edsonv
    8 months ago

    Hey @alessiogr 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?

    Screenshot_2023-04-03_at_12.13.26_PM.png
    Screenshot_2023-04-03_at_12.12.14_PM.png
    Screenshot_2023-04-03_at_12.12.36_PM.png
  • default discord avatar
    notchr
    8 months ago

    @edsonv were you following the code in my gist?

  • default discord avatar
    edsonv
    8 months ago

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

  • default discord avatar
    notchr
    8 months ago

    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

  • default discord avatar
    edsonv
    8 months ago

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

    Screenshot_2023-04-03_at_12.53.35_PM.png
  • default discord avatar
    notchr
    8 months ago

    It



    It's interesting you're getting an error



    I think I have the same code without issue



    let me check my latest



    https://gist.github.com/notchris/7ff01a0584b5a20a4a8f45c665ad1dff


    thats my current

  • default discord avatar
    edsonv
    8 months ago

    @notchr 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!

  • default discord avatar
    sssavl
    8 months ago

    Hi Guys, i use your solution @notchr and get Error: Uncaught Error: The

    useSlate

    hook must be used inside the <Slate> component's context.



    what am I doing wrong?



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

  • default discord avatar
    notchr
    8 months ago

    Sure



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

  • default discord avatar
    sssavl
    8 months ago

    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",

  • default discord avatar
    notchr
    8 months ago

    oh weird



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

  • default discord avatar
    sssavl
    8 months ago

    yeah exactly



    @notchr 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 ..

  • default discord avatar
    notchr
    8 months ago

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

  • default discord avatar
    sssavl
    5 months ago

    @notchr 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?

  • default discord avatar
    notchr
    5 months ago

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

Open the post
Continue the discussion in Discord
Like what we're doing?
Star us on GitHub!

Star

Connect with the Payload Community on Discord

Discord

online

Can't find what you're looking for?

Get help straight from the Payload team with an Enterprise License.