gehrisandro / tailwind-merge-laravel

Automatically resolves Tailwind CSS class conflicts in Laravel
MIT License
293 stars 13 forks source link

Using Laravel's conditional classes with twMerge #19

Closed dextermb closed 2 months ago

dextermb commented 2 months ago

Coming from a JavaScript background we have packages such as clsx which when combined with tailwind-merge allows us to create a conditional class function that deals with merges as well.

For example:

const cn = (...args) => twMerge(clsx(...args))

cn('block bg-stone-100', danger && 'bg-red-500')
// or
cn('block bg-stone-100', { 'bg-red-500': danger })

which would output either:

const danger = false
// block bg-stone-100

const danger = true
// block bg-red-500

Laravel describes a similar behaviour with their built-in class handling called "Conditional Merge Classes" (docs).

$theme = 'danger';

$attributes->class([
  'block bg-stone-200',
  'bg-red-500' => $theme == 'danger'
]);
// block bg-stone-200 bg-red-500

But when applying the same to twMerge it seems like the original behaviour from class is ignored:

$theme = 'danger';

$attributes->twMerge([
  'block bg-stone-200',
  'bg-red-500' => $theme == 'danger'
]);
// block bg-stone-200 1
// NOT: block bg-red-500

Would we be able to have the original behaviour built into the package to allow for conditional classes?

dextermb commented 2 months ago

As a follow up to my original comment I've created my own attribute bag helper, which allows for boolean conditions one level deep:

ComponentAttributeBag::macro("cn", function (
  ...$args
): ComponentAttributeBag {
  /** @var ComponentAttributeBag $this */
  $attr = $this->get("class", "");

  $attr .= collect($args)->reduce(function (string $carry, mixed $arg) {
    if (!$arg) {
      return $carry;
    }

    if (is_array($arg)) {
      $reduced = collect($arg)
        ->filter(fn(mixed $boolish) => !!$boolish)
        ->map(fn(mixed $a, mixed $b) => is_numeric($b) ? $a : $b)
        ->join(" ");

      return $carry . " " . $reduced;
    }

    return $carry . " " . $arg;
  }, "");

  $this->offsetSet("class", twMerge($attr));

  return $this;
});
eznix86 commented 2 months ago

As a follow up to my original comment I've created my own attribute bag helper, which allows for boolean conditions one level deep:

ComponentAttributeBag::macro("cn", function (
  ...$args
): ComponentAttributeBag {
  /** @var ComponentAttributeBag $this */
  $attr = $this->get("class", "");

  $attr .= collect($args)->reduce(function (string $carry, mixed $arg) {
    if (!$arg) {
      return $carry;
    }

    if (is_array($arg)) {
      $reduced = collect($arg)
        ->filter(fn(mixed $boolish) => !!$boolish)
        ->map(fn(mixed $a, mixed $b) => is_numeric($b) ? $a : $b)
        ->join(" ");

      return $carry . " " . $reduced;
    }

    return $carry . " " . $arg;
  }, "");

  $this->offsetSet("class", twMerge($attr));

  return $this;
});

So the usage is:

$attributes->cn([....])  // ?
dextermb commented 2 months ago

So the usage is:


$attributes->cn([....])  // ?

Yep. Please see examples in my opening post

eznix86 commented 2 months ago

can we have a $attributes->cnFor mixing what you did with twMergeFor ?

dextermb commented 2 months ago

can we have a $attributes->cnFor mixing what you did with twMergeFor ?

Ah nice, if it's already a thing then awesome.