Created
May 1, 2025 10:28
-
-
Save willvincent/2b6d3226a7cb97cbd8e53543ca5b9d74 to your computer and use it in GitHub Desktop.
Import Phosphor Icons as flux icons
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
namespace App\Console\Commands; | |
use Illuminate\Console\Command; | |
use Illuminate\Filesystem\Filesystem; | |
use function Laravel\Prompts\confirm; | |
use function Laravel\Prompts\error; | |
use function Laravel\Prompts\info; | |
use function Laravel\Prompts\intro; | |
use function Laravel\Prompts\note; | |
use function Laravel\Prompts\text; | |
class Phosphoric extends Command | |
{ | |
protected $signature = 'phosphor:icon {icons?*} {--all : Import all available icons}'; | |
protected $description = 'Import third-party icons from Phosphor.'; | |
protected string $iconSourcePath = './phosphor-icons'; // local SVG path | |
public function handle(): void | |
{ | |
if ($this->option('all')) { | |
$this->importAllIcons(); | |
return; | |
} | |
$icons = $this->argument('icons'); | |
if (count($icons)) { | |
foreach ($icons as $icon) { | |
$this->publishIcon($icon); | |
} | |
return; | |
} | |
intro('Import icons from Phosphor'); | |
note('https://phosphoricons.com'); | |
prompt: | |
$icon = text( | |
label: 'Enter icon name to import:', | |
required: 'An icon name is required.', | |
placeholder: 'e.g. airplane' | |
); | |
$this->publishIcon($icon); | |
if (confirm('Import another icon?')) { | |
goto prompt; | |
} | |
} | |
protected function importAllIcons(): void | |
{ | |
$fillDir = base_path("{$this->iconSourcePath}/fill"); | |
$regularDir = base_path("{$this->iconSourcePath}/regular"); | |
$fillIcons = collect(scandir($fillDir)) | |
->filter(fn($file) => str_ends_with($file, '.svg')) | |
->map(fn($file) => pathinfo($file, PATHINFO_FILENAME)); | |
$regularIcons = collect(scandir($regularDir)) | |
->filter(fn($file) => str_ends_with($file, '.svg')) | |
->map(fn($file) => pathinfo($file, PATHINFO_FILENAME)); | |
$shared = $fillIcons->intersect($regularIcons)->sort()->values(); | |
if ($shared->isEmpty()) { | |
error('No icons found in both fill/ and regular/'); | |
return; | |
} | |
info("Importing " . $shared->count() . " icons..."); | |
foreach ($shared as $icon) { | |
$this->publishIcon($icon); | |
} | |
info("All icons imported."); | |
} | |
protected function publishIcon(string $icon): void | |
{ | |
$fillPath = base_path("{$this->iconSourcePath}/fill/{$icon}.svg"); | |
$regularPath = base_path("{$this->iconSourcePath}/regular/{$icon}.svg"); | |
if (!file_exists($fillPath) || !file_exists($regularPath)) { | |
error("Icon '{$icon}' not found in both styles."); | |
return; | |
} | |
$fillSvg = $this->prepareSvg(file_get_contents($fillPath)); | |
$regularSvg = $this->prepareSvg(file_get_contents($regularPath)); | |
$blade = $this->generateBlade($regularSvg, $fillSvg); | |
$target = resource_path("views/flux/icon/{$icon}.blade.php"); | |
(new Filesystem)->ensureDirectoryExists(dirname($target)); | |
file_put_contents($target, $blade); | |
info("Published icon: {$target}"); | |
} | |
protected function prepareSvg(string $svg): string | |
{ | |
preg_match('/<svg[^>]*viewBox="([^"]+)"[^>]*>/i', $svg, $viewBoxMatch); | |
$viewBox = $viewBoxMatch[1] ?? '0 0 256 256'; | |
$bladeSvgOpen = <<<SVG | |
<svg {{ \$attributes->class(\$classes) }} xmlns="http://www.w3.org/2000/svg" viewBox="{$viewBox}" fill="currentColor" aria-hidden="true" data-slot="icon" data-flux-icon> | |
SVG; | |
return str($svg) | |
->replaceMatches('/<svg[^>]*>/i', $bladeSvgOpen) | |
->trim() | |
->toString(); | |
} | |
protected function generateBlade(string $regularSvg, string $fillSvg): string | |
{ | |
return <<<BLADE | |
{{-- Credit: Phosphor Icons (https://phosphoricons.com) --}} | |
@props([ | |
'variant' => 'outline', | |
]) | |
@php | |
\$classes = Flux::classes('shrink-0') | |
->add(match(\$variant) { | |
'outline' => '[:where(&)]:size-6', | |
'solid' => '[:where(&)]:size-6', | |
'mini' => '[:where(&)]:size-5', | |
'micro' => '[:where(&)]:size-4', | |
}); | |
@endphp | |
<?php switch (\$variant): case ('outline'): ?> | |
{$regularSvg} | |
<?php break; ?> | |
<?php case ('solid'): ?> | |
{$fillSvg} | |
<?php break; ?> | |
<?php case ('mini'): ?> | |
{$fillSvg} | |
<?php break; ?> | |
<?php case ('micro'): ?> | |
{$fillSvg} | |
<?php break; ?> | |
<?php endswitch; ?> | |
BLADE; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Usage
SVGs Flat
to wherever you want to load them from in the script - its' set to pull from the root of your laravel install in aphosphor-icons
directory. All you need from the SVGs Flat directory are thefill
andregular
subdirectories.So you final form will look like this, somewhere on disk (update the script to point at it):
Once there, and the artisan command pointed at the directory, and installed into app/Console/Commands/Phosphoric.php...
Usage with flux is then exactly the same as all the heroicons flux ships with: