zachdaniel / tails

Utilities for working with tailwind classes, like semantic merge, and conditional class lists.
MIT License
85 stars 9 forks source link

Class merge issue with tailwind colour extensions #14

Closed GorillaCoffee closed 7 months ago

GorillaCoffee commented 10 months ago

Describe the bug The classes/1 method (or matter of fact, merge/2) eagerly overrides classes sharing the same prefix when the array passed in argument contains a class referencing a custom colour.

To Reproduce Have a colour extension set up in the tailwind.config.js of your project, such as:

module.exports = {
  theme: {
    extend: {
      colors: {
        primary: require('tailwindcss/colors').gray,
      }
    }
  },
}

Then call, for example :

> Tails.classes(["text-md", "text-primary"])
"text-primary"

The "text-md" class is no longer present. The issue seems to present itself in other cases, such as:

> Tails.classes(["border-2", "border-primary"])
"border-primary"

Expected behavior In the same example above, the "text-md" class should be present in the returned string :

> Tails.classes(["text-md", "text-primary"])
"text-md text-primary"

Runtime

Additional context Let me know where I can help. Thank-you!

zachdaniel commented 10 months ago

🤔 this is pretty interesting. I'm going to have to look into what we could do here, but the basic issue is that we expect colors to be done using the method illustrated here: https://github.com/zachdaniel/tails#colors

That way, we know what your custom colors will be and can include them in the merge logic. Unfortunately, you couldn't do require('tailwindcss/colors').gray in that case. We'd probably need to support some kind of separate config if you want to do it that way, i.e config :tails, custom_colors: ["primary"]

marcofiset commented 7 months ago

I have the same issue. I did read about the colors config file, but I don't really want to do that. tailwind.config.js works perfectly fine and I feel like introducing a json file to declare my colors would be a step backwards.

What I don't understand, is that it works perfectly for bg and border.

zachdaniel commented 7 months ago

The basic conflict comes down to the fact that, for merging logic, we have to know when the suffix refers to a color (i.e border-primary) or something else (i.e border-2). bg doesn't have a catch-all numeric size variant that could conflict with a custom color.

in the case of

border-[#243c5a]
border-[10px]

we have to know which one we're talking about.

We don't currently have an "is size or is color" parser to distinguish these, and technically a perfect one can't be written. Because you could make a color called 2 and then say border-2. I'm not sure what would actually happen if you did that, but the point is that it's not a super easy fix. We can fix the non custom value version by detecting numbers (and a special carve out for divide-x-reverse and divide-y-reverse.

However, detecting custom values like that would require a size parser and/or a color parser (whichever is easier, probably size as it may just be "ends with px/em" or something).

zachdaniel commented 7 months ago

Okay, I was wrong, it's easier to match on custom colors, as that can be reasonably done with #, rgb(, rgba(, hsla(, currentcolor and one of the predefined CSS colors like black.

zachdaniel commented 7 months ago

....and we already have logic in to do exactly that for custom colors :) So it is perhaps sufficient for 99% of cases to just add matching for directional values starting with an integer and then we can wash our hands of it. As long as custom colors don't start with a number, I think this covers the cases.

zachdaniel commented 7 months ago

Okay, so I've made some changes that alleviate some of the issues, but not all. Without knowing that "some class we don't recognize" is a "custom color", we can't properly merge them. i.e

classes(["bg-specialblue", "bg-specialred"]

would produce "bg-specialblue bg-specialred". What I will add is a configuration option that defaults to false (for backwards compatibility purposes at a minimum) to assume that any remaining non-parsed classes that start w/ a prefix that contains colors is a color.

zachdaniel commented 7 months ago

sigh, so an example of an issue that arises here is us not being able to tell the difference between a custom font size utilities and custom text colors.

For example, with text-blorange and text-really-big. Without knowing a constant set of values for one of those, it's pretty much dead in the water. And since colors are globally used, it makes more sense to configure those statically IMO. the colors file is just one way of doing that.

I've made some changes that will make it behave better with custom colors that aren't statically defined, but I've also added some docs to show examples of what we can't figure out without a static set of custom colors provided to us. If we want to do this without the colors file, then we'd need to ship this with a js runtime or call out to node and eval the file or something, which is obviously not going to happen :)

zachdaniel commented 7 months ago

I'm going to close this for now, because I can't think of anything we can do beyond requiring a colors.json file. If someone can think of a way past the example I documented without requiring a colors.js file then we can talk about that. It is possible that we could hard-code the default set of things like font size utilities, and make those configurable, but we'll always need to know one or the other statically.

zachdaniel commented 7 months ago

relevant documentation that was added: https://github.com/zachdaniel/tails/blob/main/README.md#colors