Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce CVA to html-extra #4006

Open
wants to merge 2 commits into
base: 3.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
112 changes: 112 additions & 0 deletions extra/html-extra/CVA.php
@@ -0,0 +1,112 @@
<?php

/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Twig\Extra\Html;

final class CVA
{
private $base;
private $variants;
private $compoundVariants;
private $defaultVariants;

/**
* @var string|list<string|null>|null
* @var array<string, array<string, string|list<string|null>>|null the array should have the following format [variantCategory => [variantName => classes]]
* ex: ['colors' => ['primary' => 'bleu-8000', 'danger' => 'red-800 text-bold'], 'size' => [...]]
* @var array<array<string, string|array<string>>> the array should have the following format ['variantsCategory' => ['variantName', 'variantName'], 'class' => 'text-red-500']
* @var array<string, string>|null
*/
public function __construct(
$base = null,
$variants = null,
$compoundVariants = null,
$defaultVariants = null
) {
$this->base = $base;
$this->variants = $variants;
$this->compoundVariants = $compoundVariants;
$this->defaultVariants = $defaultVariants;
}

public function apply(array $recipes, string ...$classes): string
{
return trim($this->resolve($recipes).' '.implode(' ', $classes));
}

public function resolve(array $recipes): string
{
if (\is_array($this->base)) {
$classes = implode(' ', $this->base);
} else {
$classes = $this->base ?? '';
}

foreach ($recipes as $recipeName => $recipeValue) {
if (!isset($this->variants[$recipeName][$recipeValue])) {
continue;
}

if (\is_string($this->variants[$recipeName][$recipeValue])) {
$classes .= ' '.$this->variants[$recipeName][$recipeValue];
} else {
$classes .= ' '.implode(' ', $this->variants[$recipeName][$recipeValue]);
}
}

if (null !== $this->compoundVariants) {
foreach ($this->compoundVariants as $compound) {
$isCompound = true;
foreach ($compound as $compoundName => $compoundValues) {
if ('class' === $compoundName) {
continue;
}

if (!isset($recipes[$compoundName])) {
$isCompound = false;
break;
}

if (!\in_array($recipes[$compoundName], $compoundValues)) {
$isCompound = false;
break;
}
}

if ($isCompound) {
if (!isset($compound['class'])) {
throw new \LogicException('A compound recipe matched but no classes are registered for this match');
}

if (!\is_string($compound['class']) && !\is_array($compound['class'])) {
throw new \LogicException('The class of a compound recipe should be a string or an array of string');
}

if (\is_string($compound['class'])) {
$classes .= ' '.$compound['class'];
} else {
$classes .= ' '.implode(' ', $compound['class']);
}
}
}
}

if (null !== $this->defaultVariants) {
foreach ($this->defaultVariants as $defaultVariantName => $defaultVariantValue) {
if (!isset($recipes[$defaultVariantName])) {
$classes .= ' '.$this->variants[$defaultVariantName][$defaultVariantValue];
}
}
}

return trim($classes);
}
}
24 changes: 24 additions & 0 deletions extra/html-extra/HtmlExtension.php
Expand Up @@ -37,6 +37,7 @@ public function getFunctions(): array
{
return [
new TwigFunction('html_classes', [self::class, 'htmlClasses']),
new TwigFunction('html_cva', [$this, 'cva']),
];
}

Expand Down Expand Up @@ -110,4 +111,27 @@ public static function htmlClasses(...$args): string

return implode(' ', array_unique($classes));
}

/**
* @param array{
* base: string|list<string|null>|null,
* variants: array<string, array<string, string|array<string>>>,
* compoundVariants: array<array<string, string|array<string>>>,
* defaultVariants: array<string, string>
* } $cva
*
* base some base class you want to have in every matching recipes
* variants your recipes class
* compoundVariants compounds allow you to add extra class when multiple variation are matching in the same time
* defaultVariants allow you to add a default class when no recipe is matching
*/
public function cva(array $cva): CVA
{
return new CVA(
$cva['base'] ?? null,
$cva['variants'] ?? null,
$cva['compoundVariants'] ?? null,
$cva['defaultVariants'] ?? null
);
}
}
3 changes: 3 additions & 0 deletions extra/html-extra/README.md
Expand Up @@ -9,5 +9,8 @@ This package is a Twig extension that provides the following:
* [`html_classes`][2] function: returns a string by conditionally joining class
names together.

* [`html_cva`][3] function: return a CVA object to handle complex class variants.

[1]: https://twig.symfony.com/data_uri
[2]: https://twig.symfony.com/html_classes
[3]: https://symfony.com/bundles/ux-twig-component/current/index.html#component-with-complex-variants-cva