Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ The full icon pack can be previewed here: [HeroIcons.com](https://heroicons.com)

This pack allows you to modify: size, stroke(colour), stroke width, width and height, and style (outlined or solid) for all the icons in an easy package with easy searching of available icons.

> **New in .NET 9**
>
> The component library now targets .NET 9 and embeds the SVG assets directly in the assembly. This allows `TailBlazorHeroIcon` to render during Blazor's new `InteractiveAuto` mode without making HTTP requests so it works seamlessly in both server-side and client-side rendering scenarios.


![Nuget](https://img.shields.io/nuget/v/TailBlazor.HeroIcons.svg)

Expand Down Expand Up @@ -48,10 +52,10 @@ By default you don't need to include anything but the `Icon` parameter. However

Parameter | Default Value
--- | ---
`Width` | `64`
`Height` | `64`
`Width` | `24`
`Height` | `24`
`StrokeWidth` | `2`
`Class` | `text-black`
`Class` | inherits the current text colour (e.g. `text-slate-500`)
`IconStyle` | `IconStyle.Outline`
`EnableComments` | `false`

Expand Down
41 changes: 19 additions & 22 deletions src/Samples/WASM-PWA-Sample/WASM-PWA-Sample/WASM-PWA-Sample.csproj
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<RestoreSources>$(RestoreSources);/Users/{REPLACEWITHUSER}/Developer/TailBlazor/HeroIcons/src/TailBlazor.HeroIcons/bin/release;https://api.nuget.org/v3/index.json</RestoreSources>

<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>

</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="5.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="5.0.3" PrivateAssets="all" />
<PackageReference Include="System.Net.Http.Json" Version="5.0.0" />
<PackageReference Include="TailBlazor.HeroIcons" Version="1.1.6" />
</ItemGroup>

<ItemGroup>
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" />
</ItemGroup>

</Project>
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.0" PrivateAssets="all" />
<PackageReference Include="System.Net.Http.Json" Version="9.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../../TailBlazor.HeroIcons/TailBlazor.HeroIcons.csproj" />
</ItemGroup>
<ItemGroup>
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" />
</ItemGroup>
</Project>
12 changes: 7 additions & 5 deletions src/TailBlazor.HeroIcons/TailBlazor.HeroIcons.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageId>TailBlazor.HeroIcons</PackageId>
<RootNamespace>TailBlazor.HeroIcons</RootNamespace>
<Version>1.1.6</Version>
Expand All @@ -24,9 +26,9 @@
<SupportedPlatform Include="browser" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="5.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Components" Version="5.0.2" />
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
<EmbeddedResource Include="wwwroot/icons/**/*.svg">
<LogicalName>TailBlazor.HeroIcons.icons.%(RecursiveDir)%(Filename)%(Extension)</LogicalName>
</EmbeddedResource>
Comment on lines +29 to +31

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 Badge Align embedded SVG names with lookup format

The project now embeds the SVGs with LogicalName constructed from %(RecursiveDir), which preserves the directory separator. That yields resource names like TailBlazor.HeroIcons.icons.outline/academic-cap.svg, but the runtime lookup in TailBlazorHeroIconBase requests TailBlazor.HeroIcons.icons.{style}.{icon}.svg (dots instead of /). As a result GetManifestResourceStream never finds any icon and the component throws the InvalidOperationException for every render. Replace the slash in %(RecursiveDir) with a dot or adjust the lookup string so the resource names match.

Useful? React with 👍 / 👎.

</ItemGroup>
<ItemGroup>
<None Include="../../logo.png" Pack="true" PackagePath="">
Expand All @@ -36,4 +38,4 @@
<LinkBase>assets</LinkBase>
</None>
</ItemGroup>
</Project>
</Project>
77 changes: 39 additions & 38 deletions src/TailBlazor.HeroIcons/TailBlazorHeroIcon.razor.cs
Original file line number Diff line number Diff line change
@@ -1,74 +1,75 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System;
using System.Reflection;
using System.Threading;
using System.Xml.Linq;
using Microsoft.AspNetCore.Components;
using System.Threading.Tasks;
using System.Threading;
using System.IO;

namespace TailBlazor.HeroIcons
{
public class TailBlazorHeroIconBase : ComponentBase
{

[Inject] protected NavigationManager NavigationManager { get; set; }
private const string ResourcePrefix = "TailBlazor.HeroIcons.icons.";
private static readonly Assembly IconAssembly = typeof(TailBlazorHeroIconBase).Assembly;

[Parameter] public HeroIcon Icon { get; set; }
[Parameter] public int Width { get; set; } = 24;
[Parameter] public int Height { get; set; } = 24;
[Parameter] public int StrokeWidth { get; set; } = 2;
[Parameter] public string Class { get; set; }
[Parameter] public string? Class { get; set; }
[Parameter] public IconStyle IconStyle { get; set; } = IconStyle.Outline;
[Parameter] public bool EnableComments { get; set; } = false;
[Parameter] public bool EnableComments { get; set; }

protected string _svgIcon = string.Empty;
protected string _svgIconComment = string.Empty;

protected string _classStroke = "stroke-current ";
protected string _svgIcon = "";
protected string _svgIconComment = "";
private string _classStroke = string.Empty;

protected override async Task OnInitializedAsync()
protected override async Task OnParametersSetAsync()
{
_classStroke += Class;
_classStroke = "stroke-current";

if (EnableComments)
if (!string.IsNullOrWhiteSpace(Class))
{
// HeroIcon: annotation (style: outlined, size: 64x64, stroke (colour): text-grey-500, stroke-width: 2)
_svgIconComment = $"<!-- TailBlazor.HeroIcon: {EnumExtension.GetEnumDescription(Icon)} (style: {IconStyle.Outline.ToString()}, size: {Height}x{Width}, stroke (colour): {Class}, stroke-width: {StrokeWidth}) -->";
_classStroke += $" {Class}";
}

var baseUri = NavigationManager.BaseUri;
var token = new CancellationToken();

var document = await Task.Run(() => {
return XDocument.Load($"{baseUri}_content/TailBlazor.HeroIcons/icons/{EnumExtension.GetEnumDescription(IconStyle)}/{EnumExtension.GetEnumDescription(Icon)}.svg");
});
if (EnableComments)
{
_svgIconComment = $"<!-- TailBlazor.HeroIcon: {EnumExtension.GetEnumDescription(Icon)} (style: {EnumExtension.GetEnumDescription(IconStyle)}, size: {Height}x{Width}, stroke (colour): {Class}, stroke-width: {StrokeWidth}) -->";
}
else
{
_svgIconComment = string.Empty;
}

var iconStyleName = EnumExtension.GetEnumDescription(IconStyle);
var iconName = EnumExtension.GetEnumDescription(Icon);
var resourceName = $"{ResourcePrefix}{iconStyleName}.{iconName}.svg";

//XDocument document =
// await XDocument.LoadAsync($"{baseUri}_content/TailBlazor.HeroIcons/icons/{EnumExtension.GetEnumDescription(IconStyle)}/{EnumExtension.GetEnumDescription(Icon)}.svg", LoadOptions.None, token);
await using var resourceStream = IconAssembly.GetManifestResourceStream(resourceName)
?? throw new InvalidOperationException($"Could not find embedded icon resource '{resourceName}'.");

XElement svg_Element = document.Root;
var document = await XDocument.LoadAsync(resourceStream, LoadOptions.PreserveWhitespace, CancellationToken.None);

svg_Element.SetAttributeValue("width", Width);
svg_Element.SetAttributeValue("height", Height);
svg_Element.SetAttributeValue("stroke", "");
svg_Element.SetAttributeValue("class", _classStroke);
if (document.Root is null)
{
throw new InvalidOperationException($"The SVG document for icon '{iconName}' is empty.");
}

IEnumerable<XElement> descendants = from path in svg_Element.Descendants() select path;
document.Root.SetAttributeValue("width", Width);
document.Root.SetAttributeValue("height", Height);
document.Root.SetAttributeValue("stroke", null);
document.Root.SetAttributeValue("class", _classStroke);

foreach (XElement path in descendants)
foreach (var path in document.Root.Descendants())
{
if (path.Attribute("stroke-width") != null)
{
path.SetAttributeValue("stroke-width", StrokeWidth);
}
if (path.Attribute("fill") != null)
{
path.SetAttributeValue("fill", StrokeWidth);
}
}

_svgIcon = svg_Element.ToString();
_svgIcon = document.Root.ToString(SaveOptions.DisableFormatting);
}
}
}