petal_components
petal_components copied to clipboard
Custom classes override existing classes (help avoid "!important")
My first naive approach would be something like this:
defmodule Class do
@doc """
Builds a class based on a set of default classes and specified overrides for them
"""
def build_class(defaults, overrides) when is_binary(defaults),
do: build_class(String.split(defaults), overrides)
def build_class(defaults, overrides) when is_binary(overrides),
do: build_class(defaults, String.split(overrides))
def build_class(defaults, overrides) when is_list(defaults) and is_list(overrides) do
defaults_map = get_class_map(defaults)
overrides_map = get_class_map(overrides)
Map.merge(defaults_map, overrides_map)
|> Map.values()
|> Enum.join(" ")
end
# Converts a list of classes to a map identified by their class name prefix
defp get_class_map(classes) do
classes
|> Enum.map(fn class -> {get_class_id(class), class} end)
|> Map.new()
end
# Gets the prefix of a class name (without the part after the last `-` character)
defp get_class_id(class) do
class
|> String.reverse()
|> String.split("-", parts: 2, trim: true)
|> case do
[class] -> class
[_ | [prefix]] -> prefix
end
|> String.reverse()
end
end
Since the class order is not important, we can split the classnames by whitespace and then put them into a map, identified by their class prefix (everything before the last -, e.g. "p-5 place-items-start" => %{"p" => "p-5", "place-items" => "place-items-start"}).
Then we just merge the overrides map over the defaults map and get the remaining values.
I am not 100% sure if this would always work with tailwindcss and the naming scheme used there.
This also would override class names like table with table-auto during merging, because they share the same prefix, which could also be undesirable.
Originally posted by @moogle19 in https://github.com/petalframework/petal_components/issues/34#issuecomment-1112892876
One example where the approach above wouldn't work (from https://tailwindcss.com/docs/reusing-styles):
<div>
<div class="flex items-center space-x-2 text-base">
<h4 class="font-semibold text-slate-900">Contributors</h4>
<span class="rounded-full bg-slate-100 px-2 py-1 text-xs font-semibold text-slate-700">204</span>
</div>
<div class="mt-3 flex -space-x-2 overflow-hidden">
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1491528323818-fdd1faba62cc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt=""/>
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt=""/>
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2.25&w=256&h=256&q=80" alt=""/>
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt=""/>
<img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1517365830460-955ce3ccd263?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt=""/>
</div>
<div class="mt-3 text-sm font-medium">
<a href="#" class="text-blue-500">+ 198 others</a>
</div>
</div>
ring-2 and ring-white would override each other.
This could be a good starting point: https://github.com/dcastil/tailwind-merge
I quickly tested the merging of ring-2 and ring-white and they seem to handle it correctly.
what about taking dasiyui approach i know maybe it is a lot work but writing one class instead of write huge chunks of classes could help to avoid this problem not just that it helps maintain you will have just logic of build the component and one class
Yeah unfortunately that might too much work for a relatively small gain. I haven't had too much of a problem in my projects with overriding styles. Is there any obvious culprits we can try and do a small fix for? Guessing padding/margin/colors is the main thing people want to override?
Rules we should try and support (taken from the tailwind-merge project mentioned above)
Last conflicting class wins
twMerge('p-5 p-2 p-4') // → 'p-4'
Allows refinements
twMerge('p-3 px-5') // → 'p-3 px-5'
twMerge('inset-x-4 right-4') // → 'inset-x-4 right-4'
Resolves non-trivial conflicts
twMerge('inset-x-px -inset-1') // → '-inset-1'
twMerge('bottom-auto inset-y-6') // → 'inset-y-6'
twMerge('inline block') // → 'block'
Supports modifiers and stacked modifiers
twMerge('p-2 hover:p-4') // → 'p-2 hover:p-4'
twMerge('hover:p-2 hover:p-4') // → 'hover:p-4'
twMerge('hover:focus:p-2 focus:hover:p-4') // → 'focus:hover:p-4'
Supports arbitrary values
twMerge('bg-black bg-[color:var(--mystery-var)]') // → 'bg-[color:var(--mystery-var)]'
twMerge('grid-cols-[1fr,auto] grid-cols-2') // → 'grid-cols-2'
Supports arbitrary properties
twMerge('[mask-type:luminance] [mask-type:alpha]') // → '[mask-type:alpha]'
twMerge('[--scroll-offset:56px] lg:[--scroll-offset:44px]')
// → '[--scroll-offset:56px] lg:[--scroll-offset:44px]'
// Don't do this!
twMerge('[padding:1rem] p-8') // → '[padding:1rem] p-8'
Watch out for mixing arbitrary properties which could be expressed as Tailwind classes. tailwind-merge does not resolve conflicts between arbitrary properties and their matching Tailwind classes to keep the bundle size small.
Supports arbitrary variants
twMerge('[&:nth-child(3)]:py-0 [&:nth-child(3)]:py-4') // → '[&:nth-child(3)]:py-4'
twMerge('dark:hover:[&:nth-child(3)]:py-0 hover:dark:[&:nth-child(3)]:py-4')
// → 'hover:dark:[&:nth-child(3)]:py-4'
// Don't do this!
twMerge('[&:focus]:ring focus:ring-4') // → '[&:focus]:ring focus:ring-4'
Similarly to arbitrary properties, tailwind-merge does not resolve conflicts between arbitrary variants and their matching predefined modifiers for bundle size reasons.
Supports important modifier
twMerge('!p-3 !p-4 p-5') // → '!p-4 p-5'
twMerge('!right-2 !-inset-x-1') // → '!-inset-x-1'
Preserves non-Tailwind classes
twMerge('p-5 p-2 my-non-tailwind-class p-4') // → 'my-non-tailwind-class p-4'
Supports custom colors out of the box
twMerge('text-red text-secret-sauce') // → 'text-secret-sauce'
Ignores undefined, null and false values
twMerge('some-class', undefined, null, false) // → 'some-class'
https://stitches.dev/ could also provide inspiration
How about doing something based on css variables instead of having an interface to manually pass classes? You could use this both for layout-technical customisations as well as aesthetics (colours and such). I'm not sure to want extent Tailwind can then still be used though...
Hmm yeah that might be too fine grained and moving away from the power of Tailwind. Actually thinking @mohammedzeglam-pg might be right in following a daisy approach. eg.
@layer components {
.btn {
@apply inline-block px-5 py-3 rounded-lg focus:outline-none focus:ring focus:ring-offset-2 uppercase tracking-wider font-semibold text-sm sm:text-base;
}
.btn-primary {
@apply bg-indigo-500 text-white hover:bg-indigo-400 focus:ring-indigo-500 focus:ring-opacity-50 active:bg-indigo-600;
}
.btn-secondary {
@apply bg-gray-300 text-gray-800 hover:bg-gray-200 focus:ring-gray-300 focus:ring-opacity-50 active:bg-gray-400;
}
}
Devs will pull in Petal Components CSS into their app.css... and hopefully can override them if need be (yet to test this).
From this video it seems you can then add classes like px-10 inline and it will override what's in the @apply collection.
https://www.youtube.com/watch?v=TrftauE2Vyk&ab_channel=TailwindLabs
This way you can do inline modifications, or if you want to drastically change a component for the whole application, you redefine the @apply block for that component in your app.css.
https://www.youtube.com/watch?v=TrftauE2Vyk&ab_channel=TailwindLabs
i think now before this major update maybe add this to default config in docs will be better solution than manual, and your idea it will be making petal component fit to any design could thing about it. i was thinking about file that just imported like tailwind to app.css. https://tailwindcss.com/docs/configuration#important
https://stitches.dev/
awesome job already done by @zachdaniel in his new project https://github.com/zachdaniel/tails
It's definitely not "done" but it's a work in progress :) contributions welcome!
It's definitely not "done" but it's a work in progress :) contributions welcome!
Nice! Seems like a great start and I think the majority of cases are just padding/margin/color merges.
I'd also eventually like to add stuff there to turn at least some of the simpler classes into in-line styles so that you can write an email template in tailwind. Not sure how well that will work, but would be pretty useful IMO.