Skip to content

Commit

Permalink
Merge pull request #1 from YieldStudio/laravel-support
Browse files Browse the repository at this point in the history
Laravel support
  • Loading branch information
JamesHemery authored Jun 20, 2023
2 parents edc6723 + 3dd7f77 commit e7d6e7f
Show file tree
Hide file tree
Showing 21 changed files with 549 additions and 177 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ jobs:
fail-fast: false
matrix:
php: [ "8.1", "8.2" ]
laravel: [ "^9.0", "^10.0" ]
dependency-version: [ prefer-lowest, prefer-stable ]

name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }}
Expand All @@ -30,6 +31,8 @@ jobs:
with:
php-version: ${{ matrix.php }}
- name: 🔮 Install dependencies
run: composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction
run: |
composer require "laravel/framework:${{ matrix.laravel }}" --dev --no-interaction --no-update
composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction
- name: 🕵️‍♂️ Run Pest Tests
run: composer test
68 changes: 55 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,76 @@
# tailwind-merge-php
<p align="center"><img src="./art/socialcard.jpg" alt="Social Card of Tailwind Merge PHP"></p>

# Efficiently merge Tailwind CSS classes in PHP without style conflicts

[![Latest Version](https://img.shields.io/github/release/yieldstudio/tailwind-merge-php?style=flat-square)](https://github.com/yieldstudio/tailwind-merge-php/releases)
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/yieldstudio/tailwind-merge-php/tests.yml?branch=main)](https://github.com/yieldstudio/tailwind-merge-php/actions/workflows/tests.yml)
[![Total Downloads](https://img.shields.io/packagist/dt/yieldstudio/tailwind-merge-php?style=flat-square)](https://packagist.org/packages/yieldstudio/tailwind-merge-php)

Utility function to efficiently merge [Tailwind CSS](https://tailwindcss.com) classes in PHP without style conflicts.

> Major version zero (0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable.
This package allows you to merge multiple [Tailwind CSS](https://tailwindcss.com) classes and automatically resolves conflicts between them without headaches.

tailwind-merge-php is a PHP port of the excellent [tailwind-merge](https://github.com/dcastil/tailwind-merge) created by [dcastil](https://github.com/dcastil).


- Supports Tailwind v3.0 up to v3.3
- First-class support for laravel
- First-class support for [Laravel](https://laravel.com/)

## Installation

composer require yieldstudio/tailwind-merge-php

## Usage

```php
use YieldStudio\TailwindMerge\TailwindMerge;
use YieldStudio\TailwindMerge\TailwindMergeConfig;

TailwindMerge::instance()->merge('px-2 py-1 bg-red hover:bg-dark-red', 'p-3 bg-[#B91C1C]');
// → 'hover:bg-dark-red p-3 bg-[#B91C1C]'
// Singleton
$twMerge = TailwindMerge::instance();
$twMerge->merge('w-8 h-8 rounded-full rounded-lg'); // w-8 h-8 rounded-lg

tw_merge('px-2 py-1 bg-red hover:bg-dark-red', 'p-3 bg-[#B91C1C]');
// → 'hover:bg-dark-red p-3 bg-[#B91C1C]'
// Custom instance
$twMerge = new TailwindMerge(TailwindMergeConfig::default()); // Config is optional
$twMerge->merge('w-8 h-8 rounded-full rounded-lg'); // w-8 h-8 rounded-lg
```

> ✍️ Complete documentation is being written
## Laravel Support

## Installation

composer require yieldstudio/tailwind-merge-php
You can publish the configuration file with:

```shell
php artisan vendor:publish --provider="YieldStudio\TailwindMerge\Laravel\TailwindMergeServiceProvider"
```

### Using the `tw` helper

```php
tw('w-8 h-8 rounded-full rounded-lg'); // w-8 h-8 rounded-lg
```
### Using Blade directive

```php
<div @tw('w-8 h-8 rounded-full rounded-lg')></div>

// will be
<div class="w-8 h-8 rounded-lg"></div>
```

### Using Blade components

```php
// avatar.blade.php
<div {{ $attributes->tw('w-8 h-8 rounded-full') }}></div>

// header.blade.php
<x-avatar class="rounded-lg" />

// will be
<div class="w-8 h-8 rounded-lg"></div>
```

## Configuration & plugins

> ✍️ Complete documentation is being written
## Unit tests

Expand Down
Binary file added art/socialcard.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 6 additions & 9 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@
"php"
],
"autoload": {
"files": [
"src/helpers.php"
],
"psr-4": {
"YieldStudio\\TailwindMerge\\": "src/"
}
Expand All @@ -38,17 +35,17 @@
"types": "phpstan analyse"
},
"require": {
"php": ">=8.1",
"illuminate/collections": "^10.4"
"php": ">=8.1"
},
"require-dev": {
"pestphp/pest": "^2.2",
"illuminate/support": "^10.4",
"pestphp/pest": "^v1|^v2.2.3",
"laravel/pint": "^1.10",
"phpstan/phpstan": "^1.10"
"phpstan/phpstan": "^1.10",
"laravel/framework": "^9|^10",
"orchestra/testbench": "7.*|8.*"
},
"suggest": {
"illuminate/support": "Required to use with Laravel."
"laravel/framework": "Want to use Tailwind Merge PHP with Laravel ?"
},
"config": {
"allow-plugins": {
Expand Down
18 changes: 18 additions & 0 deletions src/ClassContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php


declare(strict_types=1);

namespace YieldStudio\TailwindMerge;

final class ClassContext
{
public function __construct(
public bool $isTailwindClass,
public string $originalClassName,
public bool $hasPostfixModifier = false,
public ?string $modifierId = null,
public ?string $classGroupId = null,
) {
}
}
16 changes: 6 additions & 10 deletions src/ClassMapFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ protected static function processClassesRecursively(array $classGroup, ClassPart
continue;
}

$classPart->validators->push(new ClassValidator(
$classPart->validators[] = new ClassValidator(
$classGroupId,
$classDefinition
));
);

continue;
}
Expand All @@ -72,18 +72,14 @@ protected static function getPart(ClassPart $classPart, string $path): ClassPart
$currentClassPartObject = $classPart;

foreach (explode(self::CLASS_PART_SEPARATOR, $path) as $pathPart) {
if (!$currentClassPartObject) {
return $classPart;
}

if (! $currentClassPartObject->nextPart->has($pathPart)) {
$currentClassPartObject->nextPart->put($pathPart, new ClassPart());
if (! array_key_exists($pathPart, $currentClassPartObject->nextPart)) {
$currentClassPartObject->nextPart[$pathPart] = new ClassPart();
}

$currentClassPartObject = $currentClassPartObject->nextPart->get($pathPart);
$currentClassPartObject = $currentClassPartObject->nextPart[$pathPart];
}

return $currentClassPartObject ?? $classPart;
return $currentClassPartObject;
}

protected static function getPrefixedClassGroups(array $classGroups, ?string $prefix): array
Expand Down
13 changes: 4 additions & 9 deletions src/ClassPart.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,20 @@

namespace YieldStudio\TailwindMerge;

use Illuminate\Support\Collection;

final class ClassPart
{
/**
* @var Collection<string, ClassPart>
* @var array<string, ClassPart>
*/
public readonly Collection $nextPart;
public array $nextPart = [];

/**
* @var Collection<string, ClassValidator>
* @var ClassValidator[]
*/
public readonly Collection $validators;
public array $validators = [];

public function __construct(public ?string $classGroupId = null)
{

$this->nextPart = new Collection();
$this->validators = new Collection();
}

public function setClassGroupId(string $classGroupId): static
Expand Down
15 changes: 9 additions & 6 deletions src/ClassUtils.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,23 +137,26 @@ protected function getGroupRecursive(array $classParts, ClassPart $classPart): ?
}

$currentClassPart = $classParts[0];
$nextClassPartObject = $classPart->nextPart->get($currentClassPart);
$nextClassPartObject = $classPart->nextPart[$currentClassPart] ?? null;
$classGroupFromNextClassPart = $nextClassPartObject ? $this->getGroupRecursive(array_slice($classParts, 1), $nextClassPartObject) : null;

if ($classGroupFromNextClassPart) {
return $classGroupFromNextClassPart;
}

if ($classPart->validators->isEmpty()) {
if (empty($classPart->validators)) {
return null;
}

$classRest = implode('-', $classParts);

return $classPart
->validators
->first(fn (ClassValidator $classValidator) => $classValidator->rule->execute($classRest))
?->classGroupId ?? null;
foreach ($classPart->validators as $classValidator) {
if ($classValidator->rule->execute($classRest)) {
return $classValidator->classGroupId;
}
}

return null;
}

protected function getGroupIdForArbitraryProperty(string $className): ?string
Expand Down
18 changes: 18 additions & 0 deletions src/Laravel/Facades/TailwindMerge.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace YieldStudio\TailwindMerge\Laravel\Facades;

use Illuminate\Support\Facades\Facade;

/**
* @method static string merge(...$classList)
*/
class TailwindMerge extends Facade
{
protected static function getFacadeAccessor(): string
{
return 'tailwind-merge';
}
}
51 changes: 47 additions & 4 deletions src/Laravel/TailwindMergeServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,61 @@

use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
use Illuminate\View\ComponentAttributeBag;
use YieldStudio\TailwindMerge\TailwindMerge;
use YieldStudio\TailwindMerge\TailwindMergeConfig;

/**
* WIP
*/
final class TailwindMergeServiceProvider extends ServiceProvider
{
public function register(): void
{
parent::register();

$this->app->singleton(TailwindMerge::class, function (): TailwindMerge {
return new TailwindMerge(
TailwindMergeConfig::fromArray(
(array) config('tailwind-merge', []),
config('tailwind-merge.strategy', 'merge') === 'merge'
)
);
});

$this->app->alias(TailwindMerge::class, 'tailwind-merge');

require_once __DIR__ . '/helpers.php';
}

public function boot(): void
{
if ($this->app->runningInConsole()) {
$this->publishes([
__DIR__ . '/config/tailwind-merge.php' => config_path('tailwind-merge.php'),
]);
}

ComponentAttributeBag::macro('tw', function (...$args) {
/* @phpstan-ignore-next-line */
$this->attributes['class'] = app(TailwindMerge::class)->merge(
$args,
/* @phpstan-ignore-next-line */
$this->attributes['class'] ?? ''
);

return $this;
});

Blade::directive('tw', function ($expression) {
$expression = is_null($expression) ? '()' : $expression;

return "class=\"<?php echo tw_merge($expression) ?>\"";
return "class=\"<?php echo tw($expression) ?>\"";
});
}

public function provides(): array
{
return [
TailwindMerge::class,
'tailwind-merge',
];
}
}
Loading

0 comments on commit e7d6e7f

Please sign in to comment.