Compare commits

..

12 Commits

Author SHA1 Message Date
3da5e00361 Throw specific error if file is found to be empty 2024-01-11 16:22:14 +00:00
a19f1f4438 Add empty file exception 2024-01-11 16:08:18 +00:00
6e2228b835 Add empty file 2024-01-11 16:08:05 +00:00
15ee51b884 Add docblock 2024-01-11 16:07:57 +00:00
6ab2baeb4e Add a changelog 2024-01-11 16:01:50 +00:00
33892f81aa Update readme 2024-01-11 15:57:48 +00:00
bb7862c6ac Allow using a space-separated string as the value for additional classes 2024-01-11 15:57:42 +00:00
c1ac0faf78 Update readme 2023-06-27 11:41:57 +01:00
1d91b50c32 Set is_safe html to true 2023-06-27 11:33:38 +01:00
9248b51908 Try explicitly loading arguments 2023-06-27 11:30:19 +01:00
0e2ee19f8f Adjust config 2023-06-27 11:13:29 +01:00
6e357ee8b7 Adjust config 2023-06-27 10:56:06 +01:00
9 changed files with 70 additions and 35 deletions

7
CHANGELOG.md Normal file
View File

@@ -0,0 +1,7 @@
# Changelog
## [X.X.X] - XXXX-XX-XX
- Allow passing in a space-separated string to the `classes` option
## [1.0.0] - 2023-06-27
- First major version release

View File

@@ -33,11 +33,12 @@ pcm_icon:
fill-group-hover: 'group-hover:fill-red-800' fill-group-hover: 'group-hover:fill-red-800'
stroke-group-hover: 'group-hover:stroke-red-800' stroke-group-hover: 'group-hover:stroke-red-800'
``` ```
# @TODO default options
`default` - Override the default options provided to the bundle.
`directories` - Which directories to look in for icons. `directories` - Which directories to look in for icons.
`colours` - Custom colours to use when colouring icons. Because this extension relies on Tailwind classes for colouring we must specify all the classes. This is a bit awkward, but you can just copy this template replacing COLOUR as appropriate: `colours` - Custom colours to use when colouring icons. Because this extension relies on Tailwind classes for colouring we **must** explicitly list all the classes required for Tailwind to render them properly. You can just copy this template replacing COLOUR as appropriate:
```yaml ```yaml
COLOUR: COLOUR:
@@ -51,26 +52,17 @@ COLOUR:
## Options ## Options
`icon (string)` **(REQUIRED)** Icon to use, without the `.svg` extension `icon (string)` **(REQUIRED)** Icon to use, without the `.svg` extension.
`title (string)` Optional text to show on hover `title (string)` Optional text to show on hover. This can be null, but not an empty string.
`size (int)` Size of the icon in pixels `size (int)` Size of the icon in pixels.
`colour (string)` Name of the colour to use. Defaults to `primary` as it assumes you have a Tailwind colour set up called `primary`. If however you are not using `primary` to set a Tailwind primary colour in your project, you can instead set the default colour of the icon by changing what colour classes the primary colour uses. EG: `colour (string)` Name of the colour to use. Defaults to `primary` as it assumes you have a Tailwind colour set up called `primary`. If you are not using `primary` to set a Tailwind primary colour in your project you can change the default colour in the `pcm_icon.yaml` file.
```yaml
primary:
fill: 'fill-purple-700'
stroke: 'stroke-purple-700'
fill-hover: 'hover:fill-purple-700'
stroke-hover: 'hover:stroke-purple-700'
fill-group-hover: 'group-hover:fill-purple-700'
stroke-group-hover: 'group-hover:stroke-purple-700'
```
`hover (string)` Name of the colour to use when the icon is hovered over `hover (string)` Name of the colour to use when the icon is hovered over
`classes (string[])` Additional classes to add to the icon. This can cause Tailwind class collisions, so only use it as a last resort. `classes (string[]|string)` Additional classes to add to the icon as either an array of strings or a space-separated string. This can cause Tailwind class collisions, so it is not recommended to use.
## Reminders ## Reminders
Remember to add `./config/packages/*.yaml` as a value in your Tailwind config content array if it does not already exist. This will ensure Tailwind classes inside of the config file get compiled. Remember to add `./config/packages/*.yaml` as a value in your Tailwind config content array if it does not already exist. This will ensure Tailwind classes inside of the config file get compiled.

View File

@@ -7,6 +7,14 @@ services:
public: true public: true
class: Pcm\IconBundle\Twig\Extension\IconExtension class: Pcm\IconBundle\Twig\Extension\IconExtension
Pcm\IconBundle\Twig\Functions\IconExtension: Pcm\IconBundle\Twig\Extension\IconExtension:
public: false public: false
alias: pcm_icon.icon_extension alias: pcm_icon.icon_extension
Pcm\IconBundle\Twig\Runtime\IconRuntime:
tags:
- { name: twig.runtime }
arguments:
$defaultOptions: '%pcm.icon_bundle.default_options%'
$directories: '%pcm.icon_bundle.directories%'
$colours: '%pcm.icon_bundle.colours%'

View File

@@ -23,10 +23,8 @@ class PcmIconExtension extends Extension
$configuration = new Configuration(); $configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs); $config = $this->processConfiguration($configuration, $configs);
$definition = $container->getDefinition('pcm_icon.icon_extension'); $container->setParameter('pcm.icon_bundle.default_options', $config['default']);
$container->setParameter('pcm.icon_bundle.directories', $config['directories']);
$definition->addArgument($config['default']); $container->setParameter('pcm.icon_bundle.colours', $config['colours']);
$definition->addArgument($config['directories']);
$definition->addArgument($config['colours']);
} }
} }

View File

@@ -0,0 +1,7 @@
<?php
declare(strict_types=1);
namespace Pcm\IconBundle\Exception;
class EmptyFileException extends \Exception {};

View File

@@ -16,7 +16,7 @@ final class IconExtension extends AbstractExtension
public function getFunctions(): array public function getFunctions(): array
{ {
return [ return [
new TwigFunction('pcm_icon', [IconRuntime::class, 'renderIcon']) new TwigFunction('pcm_icon', [IconRuntime::class, 'renderIcon'], ['is_safe' => ['html']])
]; ];
} }
} }

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Pcm\IconBundle\Twig\Runtime; namespace Pcm\IconBundle\Twig\Runtime;
use Pcm\IconBundle\Exception\ColourNotFound; use Pcm\IconBundle\Exception\ColourNotFound;
use Pcm\IconBundle\Exception\EmptyFileException;
use Pcm\IconBundle\Exception\IconNotFound; use Pcm\IconBundle\Exception\IconNotFound;
use Twig\Extension\RuntimeExtensionInterface; use Twig\Extension\RuntimeExtensionInterface;
@@ -16,7 +17,7 @@ final class IconRuntime implements RuntimeExtensionInterface
'size' => null, 'size' => null,
'colour' => null, 'colour' => null,
'hover' => null, 'hover' => null,
'classes' => [], 'classes' => "",
]; ];
public function __construct(private array $defaultOptions, private array $directories, private array $colours) public function __construct(private array $defaultOptions, private array $directories, private array $colours)
@@ -26,12 +27,13 @@ final class IconRuntime implements RuntimeExtensionInterface
* @param array $options * @param array $options
* ``` * ```
* $options = [ * $options = [
* 'icon' => (string) **REQUIRED** Icon name without trailing `.svg` * 'icon' => (string) **REQUIRED** Icon name without trailing `.svg`
* 'title' => (?string) Title text to appear on mouse hover * 'title' => (?string) Title text to appear on mouse hover
* 'size' => (int) Height and width in px * 'size' => (int) Height and width in px
* 'colour' => (string) Main colour * 'colour' => (string) Main colour
* 'hover' => (?string) Hover colour * 'hover' => (?string) Hover colour
* 'classes' => (array) Additional classes to add to the icon. Not recommended. * 'classes' => (string[]|string) Additional classes to add to the icon, given as
* an array of strings or a space-separated string.
* ] * ]
* ``` * ```
* *
@@ -43,6 +45,10 @@ final class IconRuntime implements RuntimeExtensionInterface
$svg = $this->getSvg($options['icon']); $svg = $this->getSvg($options['icon']);
if (empty($svg)) {
throw new EmptyFileException(\sprintf("The file %s.svg was found, but it was empty!", $options['icon']));
}
$this->sanitiseSvg($svg); $this->sanitiseSvg($svg);
$colourClasses = $this->getColourClasses($options['colour'], $options['hover']); $colourClasses = $this->getColourClasses($options['colour'], $options['hover']);
@@ -60,9 +66,9 @@ final class IconRuntime implements RuntimeExtensionInterface
return $svg; return $svg;
} }
private function getExtraClasses(array $extraClasses): string private function getExtraClasses(array|string $extraClasses): string
{ {
return implode(' ', $extraClasses); return \is_array($extraClasses) ? implode(' ', $extraClasses) : $extraClasses;
} }
private function mergeWithDefaultOptions(array $userOptions): array private function mergeWithDefaultOptions(array $userOptions): array
@@ -103,6 +109,9 @@ final class IconRuntime implements RuntimeExtensionInterface
$this->removeBlackFillAttributes($svg); $this->removeBlackFillAttributes($svg);
} }
/**
* @throws IconNotFound when an svg file with the passed in name cannot be found
*/
private function findSvgFilepath(string $iconName): string private function findSvgFilepath(string $iconName): string
{ {
foreach ($this->directories as $directory) { foreach ($this->directories as $directory) {

View File

@@ -6,6 +6,7 @@ namespace Pcm\IconBundle\Tests\Twig\Functions;
use Pcm\IconBundle\DependencyInjection\Configuration; use Pcm\IconBundle\DependencyInjection\Configuration;
use Pcm\IconBundle\Exception\ColourNotFound; use Pcm\IconBundle\Exception\ColourNotFound;
use Pcm\IconBundle\Exception\EmptyFileException;
use Pcm\IconBundle\Exception\IconNotFound; use Pcm\IconBundle\Exception\IconNotFound;
use Pcm\IconBundle\Twig\Runtime\IconRuntime; use Pcm\IconBundle\Twig\Runtime\IconRuntime;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
@@ -50,6 +51,12 @@ class IconRuntimeTest extends TestCase
$this->icon->renderIcon(['icon' => random_bytes(8)]); $this->icon->renderIcon(['icon' => random_bytes(8)]);
} }
public function testThrowsIfPassedInAnEmptyFile(): void
{
$this->expectException(EmptyFileException::class);
$this->icon->renderIcon(['icon' => 'empty']);
}
public function testNoTitleExistsIfNotPassedIn(): void public function testNoTitleExistsIfNotPassedIn(): void
{ {
$content = $this->icon->renderIcon(['icon' => self::ICON]); $content = $this->icon->renderIcon(['icon' => self::ICON]);
@@ -225,19 +232,26 @@ class IconRuntimeTest extends TestCase
$this->assertMatchesRegularExpression('/<svg.+class=".*group-hover:stroke-white.*".*>/', $contents); $this->assertMatchesRegularExpression('/<svg.+class=".*group-hover:stroke-white.*".*>/', $contents);
} }
public function testExtraClassesThrowsIfNotAnArray(): void public function testExtraClassesThrowsIfNotAnArrayOrString(): void
{ {
$this->expectException(\TypeError::class); $this->expectException(\TypeError::class);
$this->icon->renderIcon(['icon' => self::ICON, 'classes' => 'string_value']); $this->icon->renderIcon(['icon' => self::ICON, 'classes' => 1]);
} }
public function testExtraClassesGetAdded(): void public function testExtraClassesGetAddedFromArray(): void
{ {
$contents = $this->icon->renderIcon(['icon' => self::ICON, 'classes' => ['abc', 'def']]); $contents = $this->icon->renderIcon(['icon' => self::ICON, 'classes' => ['abc', 'def']]);
$this->assertMatchesRegularExpression('/<svg.+class=".*abc.*".*>/', $contents); $this->assertMatchesRegularExpression('/<svg.+class=".*abc.*".*>/', $contents);
$this->assertMatchesRegularExpression('/<svg.+class=".*def.*".*>/', $contents); $this->assertMatchesRegularExpression('/<svg.+class=".*def.*".*>/', $contents);
} }
public function testExtraClassesGetAddedFromString(): void
{
$contents = $this->icon->renderIcon(['icon' => self::ICON, 'classes' => 'ghi jkl']);
$this->assertMatchesRegularExpression('/<svg.+class=".*ghi.*".*>/', $contents);
$this->assertMatchesRegularExpression('/<svg.+class=".*jkl.*".*>/', $contents);
}
public function testAddingExtraClassesDoesntStripAwayColourClasses(): void public function testAddingExtraClassesDoesntStripAwayColourClasses(): void
{ {
$contents = $this->icon->renderIcon(['icon' => self::ICON, 'classes' => ['abc']]); $contents = $this->icon->renderIcon(['icon' => self::ICON, 'classes' => ['abc']]);

0
tests/icons/empty.svg Normal file
View File