diff --git a/app/Console/Commands/ClearIconCache.php b/app/Console/Commands/ClearIconCache.php
new file mode 100644
index 00000000..11a6203d
--- /dev/null
+++ b/app/Console/Commands/ClearIconCache.php
@@ -0,0 +1,21 @@
+info('SVG icon cache cleared.');
+ }
+}
diff --git a/app/Providers/SvgIconServiceProvider.php b/app/Providers/SvgIconServiceProvider.php
new file mode 100644
index 00000000..2b7da9a2
--- /dev/null
+++ b/app/Providers/SvgIconServiceProvider.php
@@ -0,0 +1,22 @@
+app->singleton(SvgIconRegistry::class);
+ }
+}
diff --git a/app/View/Components/Icons/IconComponent.php b/app/View/Components/Icons/IconComponent.php
index c8a8ab22..2e89180f 100644
--- a/app/View/Components/Icons/IconComponent.php
+++ b/app/View/Components/Icons/IconComponent.php
@@ -4,18 +4,14 @@
namespace App\View\Components\Icons;
-use App\Traits\IconTrait;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
final class IconComponent extends Component
{
- use IconTrait;
-
public string $altText = '';
public string $class = '';
public string $filename = '';
- public string $iconPath = '';
public string $titleText = '';
public function __construct(
@@ -32,13 +28,11 @@ public function __construct(
public function render(): View
{
- $this->iconPath = $this->getIconPath($this->filename) ?: 'icon-path';
-
return view('components.icons.icon-component', [
- 'iconPath' => $this->iconPath,
+ 'filename' => $this->filename,
+ 'class' => $this->class,
'altText' => $this->altText,
'titleText' => $this->titleText,
- 'class' => $this->class,
]);
}
}
diff --git a/app/View/Components/Icons/SvgIconComponent.php b/app/View/Components/Icons/SvgIconComponent.php
new file mode 100644
index 00000000..e7814aee
--- /dev/null
+++ b/app/View/Components/Icons/SvgIconComponent.php
@@ -0,0 +1,44 @@
+registry = $registry;
+ $this->filename = $filename;
+ $this->class = $class;
+ $this->label = $label;
+ $this->title = $title;
+ }
+
+ public function render(): View
+ {
+ $firstRender = $this->registry->isFirstRender($this->filename);
+ $viewName = $this->registry->getViewName($this->filename);
+
+ return view($viewName, [
+ 'class' => $this->class,
+ 'firstRender' => $firstRender,
+ 'label' => $this->label,
+ 'title' => $this->title,
+ ]);
+ }
+}
diff --git a/app/View/Components/Icons/SvgIconRegistry.php b/app/View/Components/Icons/SvgIconRegistry.php
new file mode 100644
index 00000000..bcd56ed3
--- /dev/null
+++ b/app/View/Components/Icons/SvgIconRegistry.php
@@ -0,0 +1,170 @@
+used[$filename]);
+ $this->used[$filename] = true;
+ return $firstRender;
+ }
+
+ /**
+ * Returns the name of the view that renders the icon with this filename.
+ *
+ * If the view has not been compiled yet, or is out-of-date, it will be
+ * compiled and saved to the icon-cache directory.
+ *
+ * @param string $filename The filename of the icon to get the view name for.
+ * @return string The name of the view that renders the icon.
+ */
+ public function getViewName(string $filename): string
+ {
+ if (isset($this->compiled[$filename])) {
+ return $this->compiled[$filename];
+ }
+
+ $sourcePath = base_path(implode(DIRECTORY_SEPARATOR, [self::ICON_DIRECTORY, "{$filename}.svg"]));
+
+ $viewId = Str::slug($filename);
+ $compiledPath = storage_path(implode(DIRECTORY_SEPARATOR, [self::BLADE_DIRECTORY, self::VIEW_PARENT_PATH, "{$viewId}.blade.php"]));
+
+ if (!file_exists($sourcePath)) {
+ throw new \RuntimeException("SVG icon not found at {$sourcePath} for {$filename}");
+ }
+
+ if (!file_exists($compiledPath) || (filemtime($sourcePath) > filemtime($compiledPath))) {
+ $svg = file_get_contents($sourcePath);
+ $compiled = $this->transformSvgToBlade($svg, $viewId);
+
+ @mkdir(dirname($compiledPath), 0777, true);
+ file_put_contents($compiledPath, $compiled);
+ }
+
+ $this->compiled[$filename] = implode('.', [self::VIEW_PARENT_PATH, $viewId]);
+
+ return $this->compiled[$filename];
+ }
+
+ protected function buildSvgAttributeString(array $attributes): string
+ {
+ $attributes = array_filter($attributes);
+ if (empty($attributes)) {
+ return '';
+ }
+
+ return ' ' . implode(' ', array_map(
+ fn($key, $value) => $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"',
+ array_keys($attributes),
+ $attributes,
+ ));
+ }
+
+ /**
+ * Transforms the SVG markup into a Blade view that renders the icon.
+ *
+ * @param string $svg The SVG markup to transform.
+ * @param string $viewId The ID of the view that will render the icon.
+ * @return string The Blade view that renders the icon.
+ */
+ protected function transformSvgToBlade(string $svg, string $viewId): string
+ {
+ $dom = new \DOMDocument();
+ $dom->loadXML($svg);
+
+ $svgElement = $dom->documentElement;
+
+ // Attributes to use on uses of the SVG element.
+ $width = $svgElement->getAttribute('width');
+ $height = $svgElement->getAttribute('height');
+
+ // Attributes to copy to the SVG symbol
+ $viewBox = $svgElement->getAttribute('viewBox');
+ $preserveAspectRatio = $svgElement->getAttribute('preserveAspectRatio');
+
+ // Extract title if present to use as a default title for the icon.
+ $titleElement = $svgElement->getElementsByTagName('title')->item(0);
+ $title = $titleElement ? $titleElement->textContent : '';
+
+ // Extract the aria-label if present to use as a default label for the icon.
+ $label = $svgElement->getAttribute('aria-label');
+
+ $output = "@php \$title ??= '" . addslashes($title) . "'; @endphp\n";
+ $output .= "@php \$titleId = '$viewId-' . uniqid(); @endphp\n";
+ $output .= "@php \$label ??= '" . addslashes($label) . "'; @endphp\n";
+
+ $output .= '\n";
+
+ return $output;
+ }
+}
diff --git a/bootstrap/providers.php b/bootstrap/providers.php
index 6bc212a7..ea04cb73 100644
--- a/bootstrap/providers.php
+++ b/bootstrap/providers.php
@@ -8,6 +8,7 @@
use App\Providers\FortifyServiceProvider;
use App\Providers\HorizonServiceProvider;
use App\Providers\RepositoryServiceProvider;
+use App\Providers\SvgIconServiceProvider;
use App\Providers\TelescopeServiceProvider;
use App\Providers\ViewComposerServiceProvider;
@@ -18,6 +19,7 @@
FortifyServiceProvider::class,
HorizonServiceProvider::class,
RepositoryServiceProvider::class,
+ SvgIconServiceProvider::class,
TelescopeServiceProvider::class,
ViewComposerServiceProvider::class,
];
diff --git a/public_html/images/icons/person.svg b/public_html/images/icons/person.svg
index 98ea060f..e9c67a3b 100644
--- a/public_html/images/icons/person.svg
+++ b/public_html/images/icons/person.svg
@@ -1,3 +1,3 @@
-