Skip to content

Commit e3cbff4

Browse files
committed
wip
1 parent 9aeb727 commit e3cbff4

File tree

3 files changed

+82
-47
lines changed

3 files changed

+82
-47
lines changed

src/Tempest/View/src/Components/Icon.php

Lines changed: 59 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,10 @@
1515
use Tempest\View\Elements\ViewComponentElement;
1616
use Tempest\View\IconConfig;
1717
use Tempest\View\ViewComponent;
18+
use function Tempest\get;
1819

1920
final readonly class Icon implements ViewComponent
2021
{
21-
public function __construct(
22-
private AppConfig $appConfig,
23-
private IconCache $iconCache,
24-
private IconConfig $iconConfig,
25-
private HttpClient $http,
26-
) {}
27-
2822
public static function getName(): string
2923
{
3024
return 'x-icon';
@@ -35,69 +29,88 @@ public function compile(ViewComponentElement $element): string
3529
$name = $element->getAttribute('name');
3630
$class = $element->getAttribute('class');
3731

38-
$svg = $this->render($name);
32+
return sprintf(
33+
'<?= %s::render(%s, \'%s\') ?>',
34+
self::class,
35+
// Having to replace `<?=` is a bit of a hack and should be improved
36+
str_replace(['<?=', '?>'], '', $name),
37+
$class,
38+
);
39+
}
40+
41+
/**
42+
* Renders an icon
43+
*
44+
* This method is responsible for rendering the icon. If the icon is not
45+
* in the cache, it will download it on the fly and cache it for future
46+
* use. If the icon is already in the cache, it will be served from there.
47+
*/
48+
public static function render(string $name, ?string $class): ?string
49+
{
50+
$svg = self::svg($name);
51+
// We can't use injection because we don't have an instance of this view component at runtime. Might be worth refactoring, though.
52+
$appConfig = get(AppConfig::class);
3953

4054
if (! $svg) {
41-
return $this->appConfig->environment->isLocal()
55+
return $appConfig->environment->isLocal()
4256
? ('<!-- unknown-icon: ' . $name . ' -->')
4357
: '';
4458
}
4559

46-
return match ($class) {
47-
null => $svg,
48-
default => $this->injectClass($svg, $class),
49-
};
60+
if ($class !== null) {
61+
$svg = self::injectClass($svg, $class);
62+
}
63+
64+
return $svg;
5065
}
5166

52-
/**
53-
* Downloads the icon's SVG file from the Iconify API
54-
*/
55-
private function download(string $prefix, string $name): ?string
67+
private static function svg(string $name): ?string
5668
{
57-
try {
58-
$url = new ImmutableString($this->iconConfig->iconifyApiUrl)
59-
->finish('/')
60-
->append("{$prefix}/{$name}.svg")
61-
->toString();
69+
$iconCache = get(IconCache::class);
6270

63-
$response = $this->http->get($url);
71+
try {
72+
$parts = explode(':', $name, 2);
6473

65-
if ($response->status !== Status::OK) {
74+
if (count($parts) !== 2) {
6675
return null;
6776
}
6877

69-
return $response->body;
78+
[$prefix, $name] = $parts;
79+
80+
return $iconCache->resolve(
81+
key: "iconify-{$prefix}-{$name}",
82+
cache: fn () => self::download($prefix, $name),
83+
expiresAt: $iconCache->cacheDuration
84+
? new DateTimeImmutable()
85+
->add(DateInterval::createFromDateString("{$iconCache->cacheDuration} seconds"))
86+
: null,
87+
);
7088
} catch (Exception) {
7189
return null;
7290
}
7391
}
7492

7593
/**
76-
* Renders an icon
77-
*
78-
* This method is responsible for rendering the icon. If the icon is not
79-
* in the cache, it will download it on the fly and cache it for future
80-
* use. If the icon is already in the cache, it will be served from there.
94+
* Downloads the icon's SVG file from the Iconify API
8195
*/
82-
private function render(string $name): ?string
96+
private static function download(string $prefix, string $name): ?string
8397
{
98+
$iconConfig = get(IconConfig::class);
99+
$http = get(HttpClient::class);
100+
84101
try {
85-
$parts = explode(':', $name, 2);
102+
$url = new ImmutableString($iconConfig->iconifyApiUrl)
103+
->finish('/')
104+
->append("{$prefix}/{$name}.svg")
105+
->toString();
86106

87-
if (count($parts) !== 2) {
107+
$response = $http->get($url);
108+
109+
if ($response->status !== Status::OK) {
88110
return null;
89111
}
90112

91-
[$prefix, $name] = $parts;
92-
93-
return $this->iconCache->resolve(
94-
key: "iconify-{$prefix}-{$name}",
95-
cache: fn () => $this->download($prefix, $name),
96-
expiresAt: $this->iconConfig->cacheDuration
97-
? new DateTimeImmutable()
98-
->add(DateInterval::createFromDateString("{$this->iconConfig->cacheDuration} seconds"))
99-
: null,
100-
);
113+
return $response->body;
101114
} catch (Exception) {
102115
return null;
103116
}
@@ -106,12 +119,12 @@ private function render(string $name): ?string
106119
/**
107120
* Forwards the user-provided class attribute to the SVG element
108121
*/
109-
private function injectClass(string $svg, string $class): string
122+
private static function injectClass(string $svg, string $class): string
110123
{
111124
return new ImmutableString($svg)
112125
->replace(
113-
search: '<svg ',
114-
replace: "<svg class=\"{$class}\" ",
126+
search: '<svg',
127+
replace: "<svg class=\"{$class}\"",
115128
)
116129
->toString();
117130
}

src/Tempest/View/src/Parser/TempestViewCompiler.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public function compile(string|View $view): string
4848

4949
// 5. Compile to PHP
5050
$compiled = $this->compileElements($elements);
51-
51+
lw($compiled);
5252
return $compiled;
5353
}
5454

tests/Integration/View/IconComponentTest.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,4 +161,26 @@ public function test_it_forwards_the_class_attribute(): void
161161
),
162162
);
163163
}
164+
165+
public function test_with_dynamic_data(): void
166+
{
167+
$mockHttpClient = $this->createMock(HttpClient::class);
168+
$mockHttpClient
169+
->expects($this->exactly(1))
170+
->method('get')
171+
->with('https://api.iconify.design/ph/eye.svg')
172+
->willReturn(new GenericResponse(status: Status::OK, body: '<svg></svg>'));
173+
174+
$this->container->register(HttpClient::class, fn () => $mockHttpClient);
175+
176+
$rendered = $this->render(
177+
'<x-icon :name="$iconName" class="size-5" />',
178+
iconName: "ph:eye",
179+
);
180+
181+
$this->assertSame(
182+
'<svg class="size-5"></svg>',
183+
$rendered,
184+
);
185+
}
164186
}

0 commit comments

Comments
 (0)