Skip to content
Merged
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
64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ of your codebase.
- [Removing Icons](#removing-icons)
- [Listing Icons](#listing-icons)
- [Managing Aliases](#managing-aliases)
- [Standalone Components](#standalone-components)
- [Using Icons in Components](#using-icons-in-components)
- [Restoring Icons](#restoring-icons)
- [Cleaning the Cache](#cleaning-the-cache)
Expand Down Expand Up @@ -193,6 +194,64 @@ Or assign an alias when first adding an icon:
typedicons add lucide:house --alias House
```

### Standalone Components

Standalone components let you use icons directly as named Blazor components, without
the `<Icon>` wrapper:

```razor
<MdiHome Size="5em" /> @* by set + name *@
<House Size="5em" /> @* via alias *@
```

> ⚠️ **Required setup:** Due to Blazor's source generator pipeline, standalone components
> require the following property in your `.csproj`:
> ```xml
> <PropertyGroup>
> <UseRazorSourceGenerator>false</UseRazorSourceGenerator>
> </PropertyGroup>
> ```
> Without this, standalone components will not be rendered at runtime.

Add a standalone component to an existing icon:

```bash
typedicons standalone add mdi:home
```

Remove a standalone component from an icon:

```bash
typedicons standalone remove mdi:home
```

List all icons with standalone components:

```bash
typedicons standalone list
```

Or generate a standalone component when first adding an icon:

```bash
typedicons add mdi:home --standalone
```

Combine with an alias for the cleanest usage:

```bash
typedicons add mdi:home --alias Home --standalone
```

This gives you all four ways to use the icon:

```razor
<Icon Source="Icons.Mdi.Home" /> @* full path *@
<Icon Source="Icons.Home" /> @* alias *@
<MdiHome /> @* standalone *@
<Home /> @* standalone + alias *@
```

### Using Icons in Components

Once icons are added, use them via the `<Icon>` component anywhere in your Blazor markup:
Expand Down Expand Up @@ -261,6 +320,7 @@ COMMANDS:
init Initialize TypedIcons in the current project
add <name> Add an icon by name (<set>:<icon>)
--alias <alias> Assign a shorthand alias to the icon
--standalone Generate a standalone component for the icon
remove <name> Remove an icon by name (<set>:<icon>)
list [search] List all icons in the current project
--set <set> Filter by icon set
Expand All @@ -271,6 +331,10 @@ COMMANDS:
add <name> <alias> Add an alias for an icon (<set>:<icon>)
remove <name|alias> Remove an alias by icon name (<set>:<icon>) or alias name
list [search] List all icons with aliases
standalone Manage standalone icon components
add <name> Add a standalone component for an icon (<set>:<icon>)
remove <name> Remove a standalone component from an icon (<set>:<icon>)
list [search] List all standalone icon components
```

---
Expand Down
47 changes: 1 addition & 46 deletions samples/TypedIcons.Sandbox/Components/Components/TestIcon.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;

namespace TypedIcons.Sandbox.Components.Components;

Expand All @@ -11,49 +10,5 @@ public partial class TestIcon : TestIconBase
/// <summary>The icon to render. Required.</summary>
[Parameter, EditorRequired] public TestIconDefinition Source { get; set; }

protected override void BuildRenderTree(RenderTreeBuilder builder)
{
if (Source.IsEmpty)
return;

var (width, height) = CalculateDimensions(Source);
var isDecorative = Title is null;

builder.OpenElement(0, "svg");
builder.AddAttribute(10, "xmlns", "http://www.w3.org/2000/svg");
builder.AddAttribute(20, "viewBox", Source.ViewBox);
builder.AddAttribute(30, "width", width);
builder.AddAttribute(40, "height", height);
builder.AddAttribute(50, "class", Class);
builder.AddAttribute(60, "style", Style);

if (isDecorative)
{
builder.AddAttribute(70, "aria-hidden", "true");
builder.AddAttribute(71, "focusable", "false");
}
else
{
builder.AddAttribute(70, "role", "img");
}

builder.AddMultipleAttributes(80, AdditionalAttributes);

if (!isDecorative)
{
builder.OpenElement(90, "title");
builder.AddContent(91, Title);
builder.CloseElement();
}

builder.AddMarkupContent(100, Source.SvgContent);
builder.AddContent(110, ChildContent);
builder.CloseElement();
}

private (string width, string height) CalculateDimensions(TestIconDefinition iconData) =>
Size is not null ? (Size, Size) :
Width is not null && Height is not null ? (Width, Height) :
Width is not null ? (Width, Width) :
Height is not null ? (Height, Height) : (iconData.Width, iconData.Height);
protected override TestIconDefinition IconSource => Source;
}
50 changes: 50 additions & 0 deletions samples/TypedIcons.Sandbox/Components/Components/TestIconBase.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;

namespace TypedIcons.Sandbox.Components.Components;

Expand Down Expand Up @@ -50,4 +51,53 @@ public abstract class TestIconBase : ComponentBase
/// <summary>Additional attributes passed through to the root <c>&lt;svg&gt;</c> element.</summary>
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object>? AdditionalAttributes { get; set; }

/// <summary>The icon definition to render. Provided by subclasses.</summary>
protected abstract TestIconDefinition IconSource { get; }

protected override void BuildRenderTree(RenderTreeBuilder builder)
{
if (IconSource.IsEmpty)
return;

var (width, height) = CalculateDimensions(IconSource);
var isDecorative = Title is null;

builder.OpenElement(0, "svg");
builder.AddAttribute(10, "xmlns", "http://www.w3.org/2000/svg");
builder.AddAttribute(20, "viewBox", IconSource.ViewBox);
builder.AddAttribute(30, "width", width);
builder.AddAttribute(40, "height", height);
builder.AddAttribute(50, "class", Class);
builder.AddAttribute(60, "style", Style);

if (isDecorative)
{
builder.AddAttribute(70, "aria-hidden", "true");
builder.AddAttribute(71, "focusable", "false");
}
else
{
builder.AddAttribute(70, "role", "img");
}

builder.AddMultipleAttributes(80, AdditionalAttributes);

if (!isDecorative)
{
builder.OpenElement(90, "title");
builder.AddContent(91, Title);
builder.CloseElement();
}

builder.AddMarkupContent(100, IconSource.SvgContent);
builder.AddContent(110, ChildContent);
builder.CloseElement();
}

private (string width, string height) CalculateDimensions(TestIconDefinition iconData) =>
Size is not null ? (Size, Size) :
Width is not null && Height is not null ? (Width, Height) :
Width is not null ? (Width, Width) :
Height is not null ? (Height, Height) : (iconData.Width, iconData.Height);
}
24 changes: 0 additions & 24 deletions samples/TypedIcons.Sandbox/Components/Components/TestServerIcon.cs

This file was deleted.

17 changes: 17 additions & 0 deletions samples/TypedIcons.Sandbox/Components/Components/TestStandalone.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace TypedIcons.Sandbox.Components.Components;

/// <summary>
/// Renders the <c>Server</c> icon from <c>Heroicons</c>. See <see cref="IconBase"/> for full documentation.
/// </summary>
public partial class TestHeroiconsServer : TestIconBase
{
protected override TestIconDefinition IconSource => TestIcons.Heroicons.Server;
}

/// <summary>
/// Renders the <c>Server</c> icon from <c>Heroicons</c>. See <see cref="IconBase"/> for full documentation.
/// </summary>
public partial class TestServer : TestIconBase
{
protected override TestIconDefinition IconSource => TestIcons.Heroicons.Server;
}
20 changes: 15 additions & 5 deletions samples/TypedIcons.Sandbox/Components/Pages/Home.razor
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,22 @@

<PageTitle>Home</PageTitle>

<TestIcon Source="@TestIcons.Heroicons.Server" Size="5em" Title="server icon" />
@* Test icons from sandbox *@
<TestIcon Source="TestIcons.Heroicons.Server" Size="5em"/>

<TestIcon Source="@TestIcons.Server" Size="5em" />
<TestIcon Source="TestIcons.Server" Size="5em"/>

<TestServerIcon Size="5em" />
<TestHeroiconsServer Size="5em"/>

<Icon Source="Icons.Heroicons.ArchiveBox" Size="5em" />
<TestServer Size="5em"/>

<Icon Source="Icons.House" Size="5em" />
<br/>

@* Icons from generation *@
<Icon Source="Icons.Mdi.Home" Size="5em"/>

<Icon Source="Icons.House" Size="5em"/>

<MdiHome Size="5em"/>

<House Size="5em"/>
3 changes: 3 additions & 0 deletions samples/TypedIcons.Sandbox/TypedIcons.Sandbox.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>

<!-- Disable razor source generation in order to use standalone components -->
<UseRazorSourceGenerator>false</UseRazorSourceGenerator>
</PropertyGroup>

<ItemGroup>
Expand Down
10 changes: 7 additions & 3 deletions samples/TypedIcons.Sandbox/typedicons.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@
{
"set": "mdi",
"name": "home",
"alias": "House"
"alias": "House",
"standalone": true
},
{
"set": "mdi",
"name": "account"
"name": "account",
"alias": "Account",
"standalone": true
},
{
"set": "mdi",
"name": "settings"
"name": "settings",
"standalone": true
},
{
"set": "mdi",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using Spectre.Console.Cli;
using TypedIcons.Cli.Services;

namespace TypedIcons.Cli.Commands;
namespace TypedIcons.Cli.Commands.Icon;

public class AddIconCommand(IconService iconService) : AsyncCommand<AddIconCommand.Settings>
{
Expand All @@ -16,6 +16,11 @@ public class Settings : GlobalSettings
[CommandOption("--alias")]
[Description("Assign an alias to the icon")]
public string? Alias { get; init; }

[CommandOption("--standalone")]
[Description("Add standalone component")]
[DefaultValue(false)]
public bool? Standalone { get; init; }
}

protected override async Task<int> ExecuteAsync(CommandContext context, Settings settings, CancellationToken ct)
Expand All @@ -26,7 +31,7 @@ protected override async Task<int> ExecuteAsync(CommandContext context, Settings
return -1;
}

var result = await iconService.AddIconAsync(settings.Name, settings.Alias, ct);
var result = await iconService.AddIconAsync(settings.Name, settings.Alias, settings.Standalone, ct);
return result ? 0 : -1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Spectre.Console.Cli;
using TypedIcons.Cli.Services;

namespace TypedIcons.Cli.Commands;
namespace TypedIcons.Cli.Commands.Icon;

public class ListIconsCommand(IconService iconService) : AsyncCommand<ListIconsCommand.Settings>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using Spectre.Console.Cli;
using TypedIcons.Cli.Services;

namespace TypedIcons.Cli.Commands;
namespace TypedIcons.Cli.Commands.Icon;

public class RemoveIconCommand(IconService iconService) : AsyncCommand<RemoveIconCommand.Settings>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Spectre.Console.Cli;
using TypedIcons.Cli.Services;

namespace TypedIcons.Cli.Commands;
namespace TypedIcons.Cli.Commands.Project;

public class CleanCacheCommand(IconService iconService) : AsyncCommand<CleanCacheCommand.Settings>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Spectre.Console.Cli;
using TypedIcons.Cli.Services;

namespace TypedIcons.Cli.Commands;
namespace TypedIcons.Cli.Commands.Project;

public class InitProjectCommand(InitializationService initializationService) : AsyncCommand<InitProjectCommand.Settings>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Spectre.Console.Cli;
using TypedIcons.Cli.Services;

namespace TypedIcons.Cli.Commands;
namespace TypedIcons.Cli.Commands.Project;

public class RestoreIconsCommand(IconService iconService) : AsyncCommand<RestoreIconsCommand.Settings>
{
Expand Down
Loading
Loading