Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Documentation/ViewHelpers/Fluid.json

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions Documentation/ViewHelpers/Fluid/Format/CssVariables.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.. This reStructured text file has been automatically generated, do not change.
.. Source: https://github.com/TYPO3/Fluid/blob/main/src/ViewHelpers/Format/CssVariablesViewHelper.php

:edit-on-github-link: https://github.com/TYPO3/Fluid/edit/main/src/ViewHelpers/Format/CssVariablesViewHelper.php
:navigation-title: format.cssVariables

.. include:: /Includes.rst.txt

.. _typo3fluid-fluid-format-cssvariables:

========================================================
Format.cssVariables ViewHelper `<f:format.cssVariables>`
========================================================

.. .. note::
.. This reference is part of the documentation of Fluid Standalone.
.. If you are working with Fluid in TYPO3 CMS, please refer to
.. :doc:`TYPO3's ViewHelper reference <t3viewhelper:Global/Format/CssVariables>` instead.

.. typo3:viewhelper:: format.cssVariables
:source: ../../Fluid.json
116 changes: 116 additions & 0 deletions src/ViewHelpers/Format/CssVariablesViewHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php

declare(strict_types=1);

/*
* This file belongs to the package "TYPO3 Fluid".
* See LICENSE.txt that was shipped with this package.
*/

namespace TYPO3Fluid\Fluid\ViewHelpers\Format;

use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;

/**
* ViewHelper to provide fluid variables as CSS variables.
*
* The css variable names are created from the array keys.
* You can add a custom prefix to the CSS variable name.
*
* Optionally, a CSS selector (e.g. :root) can be supplied
* which will be wrapped around the CSS variables.
*
* Examples
* ========
*
* Converting a view variable
* ------------------------
*
* ::
*
* {someArray -> f:format.cssVariables()}
*
* ``--key: value; --key2: value2;``
* Depending on the value of ``{someArray}``.
*
* Associative array without selector for inline usage
* -----------------
*
* ::
*
* {f:format.cssVariables(value: {foo: 'bar', bar: 'baz'})}
*
* ``--foo: bar; --bar: baz;``
*
* Nested array with prefix and custom selector
* ----------------------------------------
*
* ::
*
* {f:format.cssVariables(value: {foo: 'bar', bar: {baz: 'qux'}}, prefix: 'color', selector: '.my-class')}
*
* ``.my-class {
* --color-foo: bar;
* --color-bar-baz: qux;
* }``
*/
final class CssVariablesViewHelper extends AbstractViewHelper
{
/**
* @var bool
*/
protected $escapeChildren = false;

public function initializeArguments(): void
{
$this->registerArgument('value', 'array', 'Input array which should be provided as CSS variables');
$this->registerArgument('prefix', 'string', 'Prefix used in the CSS variables name', false, '');
$this->registerArgument('selector', 'string', 'Define CSS selector/s for the CSS variables', false, '');
}

public function render(): string
{
$value = $this->arguments['value'];

if ($value === null) {
$value = $this->renderChildren();
}

if (!is_iterable($value)) {
return '';
}

$prefix = is_string($this->arguments['prefix']) ? str_replace('_', '-', strtolower($this->arguments['prefix'])) : '';
if (!empty($prefix)) {
$prefix .= '-';
}

if (!empty($this->arguments['selector'])) {
return $this->arguments['selector'] . ' {' . PHP_EOL . $this->buildVariables($value, $prefix, PHP_EOL) . '}';
}

return trim($this->buildVariables($value, $prefix));
}

/**
* Explicitly set argument name to be used as content.
*/
public function getContentArgumentName(): string
{
return 'value';
}

private function buildVariables(iterable $variables, string $prefix = '', string $separator = ' '): string
{
$content = '';

foreach ($variables as $key => $value) {
if (is_iterable($value)) {
$content .= self::buildVariables($value, $prefix . $key . '-', $separator);
} elseif (is_scalar($value) || $value instanceof \Stringable) {
$content .= '--' . str_replace('_', '-', strtolower($prefix . $key)) . ': ' . $value . ';' . $separator;
}
}
return $content;
}
}
117 changes: 117 additions & 0 deletions tests/Functional/ViewHelpers/Format/CssVariablesViewHelperTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

declare(strict_types=1);

/*
* This file belongs to the package "TYPO3 Fluid".
* See LICENSE.txt that was shipped with this package.
*/

namespace TYPO3Fluid\Fluid\Tests\Functional\ViewHelpers\Format;

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use TYPO3Fluid\Fluid\Tests\Functional\AbstractFunctionalTestCase;
use TYPO3Fluid\Fluid\Tests\Functional\Fixtures\Various\IterableExample;
use TYPO3Fluid\Fluid\View\TemplateView;

final class CssVariablesViewHelperTest extends AbstractFunctionalTestCase
{
private static array $value = [
'red' => '#ff0000',
'green' => 'rgb(0, 255, 0)',
'nested' => [
'whiteColor' => '#fff',
'black_color' => '#000',
'variable' => 'var(--my-variable)',
],
'non-processable-item' => null,
];

public static function renderDataProvider(): \Generator
{
yield 'value as argument' => [
'<f:format.cssVariables value="{value}"/>',
['value' => self::$value],
'--red: #ff0000; --green: rgb(0, 255, 0); --nested-whitecolor: #fff; --nested-black-color: #000; --nested-variable: var(--my-variable);',
];
yield 'value inline' => [
'{value -> f:format.cssVariables()}',
['value' => self::$value],
'--red: #ff0000; --green: rgb(0, 255, 0); --nested-whitecolor: #fff; --nested-black-color: #000; --nested-variable: var(--my-variable);',
];
yield 'value as child' => [
'<f:format.cssVariables>{value}</f:format.cssVariables>',
['value' => self::$value],
'--red: #ff0000; --green: rgb(0, 255, 0); --nested-whitecolor: #fff; --nested-black-color: #000; --nested-variable: var(--my-variable);',
];
yield 'value as child and argument' => [
'<f:format.cssVariables value="{argument}">{child}</f:format.cssVariables>',
['argument' => ['abc' => 'argument'], 'child' => ['abc' => 'child']],
'--abc: argument;',
];
yield 'with prefix' => [
'<f:format.cssVariables value="{value}" prefix="color"/>',
['value' => self::$value],
'--color-red: #ff0000; --color-green: rgb(0, 255, 0); --color-nested-whitecolor: #fff; --color-nested-black-color: #000; --color-nested-variable: var(--my-variable);',
];
yield 'with selector' => [
'<f:format.cssVariables value="{value}" selector=".my-css-class"/>',
['value' => self::$value],
'.my-css-class {
--red: #ff0000;
--green: rgb(0, 255, 0);
--nested-whitecolor: #fff;
--nested-black-color: #000;
--nested-variable: var(--my-variable);
}',
];
yield 'with prefix and selector' => [
'<f:format.cssVariables value="{value}" prefix="color" selector=".my-css-class, #my-id"/>',
['value' => self::$value],
'.my-css-class, #my-id {
--color-red: #ff0000;
--color-green: rgb(0, 255, 0);
--color-nested-whitecolor: #fff;
--color-nested-black-color: #000;
--color-nested-variable: var(--my-variable);
}',
];
yield 'numeric array as value' => [
'<f:format.cssVariables value="{value}" selector=":root"/>',
['value' => ['foo', null, 'bar']],
':root {
--0: foo;
--2: bar;
}',
];
yield 'object as value' => [
'<f:format.cssVariables value="{value}" selector=":root"/>',
['value' => new IterableExample(self::$value)],
':root {
--red: #ff0000;
--green: rgb(0, 255, 0);
--nested-whitecolor: #fff;
--nested-black-color: #000;
--nested-variable: var(--my-variable);
}',
];
}

#[DataProvider('renderDataProvider')]
#[Test]
public function render(string $template, array $variables, $expected): void
{
$view = new TemplateView();
$view->assignMultiple($variables);
$view->getRenderingContext()->setCache(self::$cache);
$view->getRenderingContext()->getTemplatePaths()->setTemplateSource($template);
self::assertSame($expected, $view->render());

$view = new TemplateView();
$view->assignMultiple($variables);
$view->getRenderingContext()->setCache(self::$cache);
$view->getRenderingContext()->getTemplatePaths()->setTemplateSource($template);
self::assertSame($expected, $view->render());
}
}