Compare commits

..

1 Commits

Author SHA1 Message Date
f3ec9c7d4b Update README.md 2024-08-05 15:26:14 +00:00
9 changed files with 15 additions and 123 deletions

View File

@@ -1,9 +0,0 @@
# Changelog
## [X.X.X] - XXXX-XX-XX
- Allow passing in a space-separated string to the `classes` option
- Add new exceptions and messages to make debugging a little bit faster
- Set `aria-hidden` attribute depending on whether a `title` has been set
## [1.0.0] - 2023-06-27
- First major version release

View File

@@ -1,3 +1,5 @@
# DEPRECATED in favour of Symfony UX Icon
# PCM Icon Bundle # PCM Icon Bundle
Use icons inside of Twig templates with ease! Project must be using Tailwind for the colours to work properly. Use icons inside of Twig templates with ease! Project must be using Tailwind for the colours to work properly.
@@ -62,7 +64,7 @@ COLOUR:
`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[]|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. `classes (string[])` Additional classes to add to the icon. This can cause Tailwind class collisions, so only use it as a last resort.
## 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

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

View File

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

View File

@@ -5,9 +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 Pcm\IconBundle\Exception\InvalidSvgException;
use Twig\Extension\RuntimeExtensionInterface; use Twig\Extension\RuntimeExtensionInterface;
final class IconRuntime implements RuntimeExtensionInterface final class IconRuntime implements RuntimeExtensionInterface
@@ -18,7 +16,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)
@@ -28,13 +26,12 @@ 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' => (string[]|string) Additional classes to add to the icon, given as * 'classes' => (array) Additional classes to add to the icon. Not recommended.
* an array of strings or a space-separated string.
* ] * ]
* ``` * ```
* *
@@ -46,14 +43,6 @@ 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']));
}
if (!$this->isValidXml($svg)) {
throw new InvalidSvgException(\sprintf("The file %s.svg was found, but it does not contain valid SVG code!", $options['icon']));
}
$this->sanitiseSvg($svg); $this->sanitiseSvg($svg);
$colourClasses = $this->getColourClasses($options['colour'], $options['hover']); $colourClasses = $this->getColourClasses($options['colour'], $options['hover']);
@@ -62,11 +51,8 @@ final class IconRuntime implements RuntimeExtensionInterface
$classes = trim($colourClasses.' '.$extraClasses); $classes = trim($colourClasses.' '.$extraClasses);
$this->addClassesToSvg($svg, $classes); $this->addClassesToSvg($svg, $classes);
$svg = $this->addAttribute($svg, 'aria-hidden', 'true');
if (null !== $options['title']) { if (null !== $options['title']) {
$this->addTitleToSvg($svg, $options['title']); $this->addTitleToSvg($svg, $options['title']);
$svg = $this->addAttribute($svg, 'aria-hidden', 'false');
} }
$this->setSvgHeightAndWidth($svg, $options['size']); $this->setSvgHeightAndWidth($svg, $options['size']);
@@ -74,9 +60,9 @@ final class IconRuntime implements RuntimeExtensionInterface
return $svg; return $svg;
} }
private function getExtraClasses(array|string $extraClasses): string private function getExtraClasses(array $extraClasses): string
{ {
return \is_array($extraClasses) ? implode(' ', $extraClasses) : $extraClasses; return implode(' ', $extraClasses);
} }
private function mergeWithDefaultOptions(array $userOptions): array private function mergeWithDefaultOptions(array $userOptions): array
@@ -117,9 +103,6 @@ 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) {
@@ -219,23 +202,4 @@ final class IconRuntime implements RuntimeExtensionInterface
$svg = $this->removeXMLDeclaration($svgAsXml->saveXML()); $svg = $this->removeXMLDeclaration($svgAsXml->saveXML());
} }
private function addAttribute(string $svg, string $attribute, string $value): string
{
$xml = new \SimpleXMLElement($svg);
$xml = $this->addAttributeToXmlElement($xml, $attribute, $value);
return $this->removeXMLDeclaration($xml->saveXML());
}
private function isValidXml(string $input): bool
{
try {
new \SimpleXMLElement($input);
} catch (\Exception) {
return false;
}
return true;
}
} }

View File

@@ -6,9 +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\Exception\InvalidSvgException;
use Pcm\IconBundle\Twig\Runtime\IconRuntime; use Pcm\IconBundle\Twig\Runtime\IconRuntime;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
@@ -52,18 +50,6 @@ 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 testThrowsIfContentsIsNotValidSvg(): void
{
$this->expectException(InvalidSvgException::class);
$this->icon->renderIcon(['icon' => 'invalid']);
}
public function testNoTitleExistsIfNotPassedIn(): void public function testNoTitleExistsIfNotPassedIn(): void
{ {
$content = $this->icon->renderIcon(['icon' => self::ICON]); $content = $this->icon->renderIcon(['icon' => self::ICON]);
@@ -239,26 +225,19 @@ class IconRuntimeTest extends TestCase
$this->assertMatchesRegularExpression('/<svg.+class=".*group-hover:stroke-white.*".*>/', $contents); $this->assertMatchesRegularExpression('/<svg.+class=".*group-hover:stroke-white.*".*>/', $contents);
} }
public function testExtraClassesThrowsIfNotAnArrayOrString(): void public function testExtraClassesThrowsIfNotAnArray(): void
{ {
$this->expectException(\TypeError::class); $this->expectException(\TypeError::class);
$this->icon->renderIcon(['icon' => self::ICON, 'classes' => 1]); $this->icon->renderIcon(['icon' => self::ICON, 'classes' => 'string_value']);
} }
public function testExtraClassesGetAddedFromArray(): void public function testExtraClassesGetAdded(): 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']]);
@@ -280,23 +259,4 @@ class IconRuntimeTest extends TestCase
$this->assertStringNotContainsString($needleA, $contents); $this->assertStringNotContainsString($needleA, $contents);
$this->assertStringNotContainsString($needleB, $contents); $this->assertStringNotContainsString($needleB, $contents);
} }
public function testAriaHiddenAttributeIsAdded(): void
{
$contents = $this->icon->renderIcon(['icon' => self::ICON]);
$this->assertMatchesRegularExpression('/<svg.+aria-hidden="true".*>/', $contents);
}
public function testAriaHiddenAttributeIsSetToTrueIfAlreadyPresent(): void
{
$contents = $this->icon->renderIcon(['icon' => 'attr']);
$this->assertMatchesRegularExpression('/<svg.+aria-hidden="true".*>/', $contents);
$this->assertDoesNotMatchRegularExpression('/<svg.+aria-hidden="false".*>/', $contents);
}
public function testAriaHiddenAttributeIsSetToFalseIfATitleIsPassedIn(): void
{
$contents = $this->icon->renderIcon(['icon' => self::ICON, 'title' => 'something']);
$this->assertMatchesRegularExpression('/<svg.+aria-hidden="false".*>/', $contents);
}
} }

View File

@@ -1,10 +0,0 @@
<svg aria-hidden="false" xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="96 96 320 320">
<title>Some cross lol</title>
<line fill="rgb(0, 0, 0)"></line>
<line fill="#000"></line>
<line fill="#000000"></line>
<line stroke="currentColor" fill="black"></line>
<line x1="256" y1="112" x2="256" y2="400" width="101" height="101" style="fill: none; stroke: rgb(0,0, 0); stroke-linecap: round; stroke-linejoin: round; stroke-width: 32px;"></line>
<line x1="400" y1="256" x2="112" y2="256" width="102" height="102" style="fill: none; stroke: #000; stroke-linecap: round; stroke-linejoin: round; stroke-width: 32px;"></line>
<polyline points="112 244 256 100 400 244" style="fill:#000;fill:#000000;fill: black;fill:rgb(0,0,0);stroke:#000;stroke:#000000;stroke:black;stroke:rgb(0, 0, 0);stroke-linecap:round;stroke-linejoin:round;stroke-width:48px" stroke='currentColor'/>
</svg>

Before

Width:  |  Height:  |  Size: 924 B

View File

View File

@@ -1 +0,0 @@
<svg><svg>

Before

Width:  |  Height:  |  Size: 11 B