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
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ jobs:

# The project, not the solution: packing the solution warns for every non-packable project.
- name: Smoke pack
run: dotnet pack src/ShellIcons.Blazor/ShellIcons.Blazor.csproj --configuration Release --no-build --output nupkgs
run: |
dotnet pack src/ShellIcons.Blazor/ShellIcons.Blazor.csproj --configuration Release --no-build --output nupkgs
dotnet pack src/ShellIcons.Avalonia/ShellIcons.Avalonia.csproj --configuration Release --no-build --output nupkgs
env:
ContinuousIntegrationBuild: true

Expand Down
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Changelog

All notable changes to the ShellIcons packages. `ShellIcons.Blazor` is published on NuGet; `ShellIcons.Maui` is in preview and not published yet.
All notable changes to the ShellIcons packages. `ShellIcons.Blazor` is published on NuGet; `ShellIcons.Maui` and `ShellIcons.Avalonia` are in preview and not published yet.

Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows [SemVer](https://semver.org/spec/v2.0.0.html).

Expand All @@ -12,7 +12,8 @@ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versio
- **Blazor: `Icon.*` factory** — `Icon.ChevronRight()` returns a `RenderFragment`, for icons as values: component parameters and lists built in C#. Unused icons are still trimmed.
- **Blazor: clear error for `@onclick` on an icon.** On a component, Razor passes `@onclick="…"` as a plain string, which broke rendering in the browser with a cryptic error. It now throws an `InvalidOperationException` that explains the forms that work.
- **`ShellIcons.Maui` (preview, not yet on NuGet)** — the same catalog as native MAUI `Path` shapes: typed controls (`<icons:ChevronRight />`), an `IconName` dispatcher (`<icons:Icon Name="Search" />`), and `IconCatalog` with tags, categories, alias-aware `TryParse` and `Search`. Builds for Android, iOS, Mac Catalyst and Windows.
- Build diagnostics for custom icons that can't be converted for MAUI: `SHELLICONS002` (unsupported SVG such as `<g>` or `transform`), `SHELLICONS004` (name not usable as an enum member), `SHELLICONS005` (shape outside the viewBox, dropped).
- **`ShellIcons.Avalonia` (preview, not yet on NuGet)** — the same catalog for Avalonia 12 (`net8.0`): typed controls (`<icons:ChevronRight />`), a dispatcher (`<icons:Icon Kind="Search" />`) and the same `IconName` / `IconCatalog` API as the MAUI package. Icons inherit `Foreground`, so they take the color of the surrounding text.
- Build diagnostics for custom icons that can't be converted for MAUI or Avalonia: `SHELLICONS002` (unsupported SVG such as `<g>` or `transform`), `SHELLICONS004` (name not usable as an enum member), `SHELLICONS005` (shape outside the viewBox, dropped).
- Docs site at [shellicons.shellui.dev](https://shellicons.shellui.dev) (deployed from `main`), with a searchable icon browser and a MAUI section.

### Changed
Expand Down
32 changes: 23 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Lucide-derived SVG icons for **Blazor**, **Avalonia**, and **.NET MAUI**.

- Zero JS, zero required CSS
- Same visual system across all three .NET UI frameworks
- Native rendering per target: inline `<svg>` in Blazor, native `Path` shapes in Avalonia/MAUI
- Native rendering per target: inline `<svg>` in Blazor, native `Path` shapes in MAUI, direct drawing in Avalonia
- Tree-shakeable typed components per icon

Docs: [shellicons.shellui.dev](https://shellicons.shellui.dev)
Expand All @@ -17,16 +17,17 @@ Docs: [shellicons.shellui.dev](https://shellicons.shellui.dev)
|---|---|
| `ShellIcons.Blazor` | **0.1.0-alpha on [NuGet](https://www.nuget.org/packages/ShellIcons.Blazor)** — 1,555 Lucide 0.475.0 icons |
| `ShellIcons.Maui` | **Preview, unpublished** — builds for Android, iOS, Mac Catalyst and Windows; see [src/ShellIcons.Maui](src/ShellIcons.Maui/README.md) |
| `ShellIcons.Avalonia` | Planned — will reuse the MAUI path conversion |
| `ShellIcons.Avalonia` | **Preview, unpublished** — Avalonia 12, `net8.0`; see [src/ShellIcons.Avalonia](src/ShellIcons.Avalonia/README.md) |

- [x] Source generators emit every icon from the vendored catalog (`LUCIDE_VERSION.txt`) plus drop-in `catalog/custom/` icons
- [x] Blazor: suffixed components (`<ChevronRightIcon />`), `Icon.*` factory, `<ShellIcon Name>` dispatcher — safe next to UI kits
- [x] MAUI: typed controls, `IconName` dispatcher, `IconCatalog` metadata with alias lookup
- [x] 135 tests (xUnit + bUnit + headless MAUI), CI for both solutions, tag-driven release — see [RELEASING.md](RELEASING.md)
- [x] Docs site at [docs/ShellIcons.Docs](docs/ShellIcons.Docs) (ShellDocs 0.1.7-alpha), with a searchable icon browser
- [x] Avalonia: typed controls, `Kind` dispatcher, inherited `Foreground`, the same `IconCatalog`
- [x] 168 tests (xUnit + bUnit + headless MAUI + headless Avalonia with Skia rendering), CI for both solutions, tag-driven release — see [RELEASING.md](RELEASING.md)
- [x] Docs site at [docs/ShellIcons.Docs](docs/ShellIcons.Docs) (ShellDocs 0.1.11-alpha), with a searchable icon browser
- [x] Static build for GitHub Pages ([shellicons.shellui.dev](https://shellicons.shellui.dev)) — deploys on push to `main`
- [ ] MAUI: check rendering on Android/iOS devices, then publish
- [ ] Avalonia target
- [ ] Avalonia: run in a real desktop app, then publish

## Blazor quickstart

Expand Down Expand Up @@ -163,6 +164,17 @@ Without `Title` icons are decorative (`aria-hidden="true"`); with it they get `r

Not on NuGet yet — reference `src/ShellIcons.Maui` from source. Docs: [MAUI getting started](docs/ShellIcons.Docs/content/docs/maui/getting-started.md).

## Avalonia quickstart (preview)

```xml
<Window xmlns:icons="https://shellicons.dev/avalonia">
<icons:ChevronRight Size="16" />
<icons:Icon Kind="{Binding StatusIcon}" Foreground="Crimson" />
</Window>
```

Icons inherit `Foreground`, so they match the surrounding text by default. Not on NuGet yet — reference `src/ShellIcons.Avalonia` from source. Docs: [Avalonia getting started](docs/ShellIcons.Docs/content/docs/avalonia/getting-started.md).

## Repo layout

```
Expand All @@ -171,7 +183,7 @@ shell-icons/
├── RELEASING.md how to cut a release
├── LUCIDE_VERSION.txt pinned upstream tag
├── NOTICE.md Lucide ISC attribution
├── ShellIcons.slnx Blazor, generators, tests, docs — no MAUI workload needed
├── ShellIcons.slnx Blazor, Avalonia, generators, tests, docs — no MAUI workload needed
├── ShellIcons.Maui.slnx MAUI projects — needs `dotnet workload install maui`
├── Directory.Build.props
├── catalog/
Expand All @@ -185,11 +197,13 @@ shell-icons/
│ ├── ShellIcons.Blazor/ Razor Class Library (net8.0;net9.0)
│ ├── ShellIcons.Generator/ Blazor source generator (netstandard2.0)
│ ├── ShellIcons.Maui/ MAUI library (preview)
│ └── ShellIcons.Generator.Xaml/ MAUI source generator + SVG → path conversion
│ ├── ShellIcons.Avalonia/ Avalonia library (preview, net8.0)
│ └── ShellIcons.Generator.Xaml/ MAUI + Avalonia source generator, SVG → path conversion
├── tests/
│ ├── ShellIcons.Generator.Tests/ xUnit: SvgParser, Naming, path conversion
│ ├── ShellIcons.Blazor.Tests/ xUnit + bUnit: components, factory, dispatcher, collisions
│ └── ShellIcons.Maui.Tests/ headless MAUI: controls, XAML, whole-catalog geometry
│ ├── ShellIcons.Maui.Tests/ headless MAUI: controls, XAML, whole-catalog geometry
│ └── ShellIcons.Avalonia.Tests/ headless Avalonia + Skia: controls, XAML, whole-catalog rendering
├── docs/
│ └── ShellIcons.Docs/ ShellDocs site — dev at http://localhost:5145
└── .github/workflows/
Expand All @@ -211,7 +225,7 @@ Serves at http://localhost:5145. Pages live in `docs/ShellIcons.Docs/content/doc

`/docs/icons` is a searchable browser of the entire catalog with click-to-copy component names.

Build the static site the way the deploy workflow does (needs `dotnet tool install -g ShellDocs.CLI --version 0.1.7-alpha`):
Build the static site the way the deploy workflow does (needs `dotnet tool install -g ShellDocs.CLI --version 0.1.11-alpha`):

```bash
cd docs/ShellIcons.Docs
Expand Down
2 changes: 1 addition & 1 deletion RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ How to ship a new version of `ShellIcons.Blazor` to NuGet.org and create the mat

**Who can release:** anyone who can approve the `release` environment on GitHub. Contributors without that access open a PR; a maintainer does the release.

**What's released:** only `ShellIcons.Blazor`. `ShellIcons.Maui` is in preview — `release.yml` doesn't pack it yet, and publishing it will need a Windows or macOS job with the MAUI workload. The docs site isn't part of a release either: it deploys to [shellicons.shellui.dev](https://shellicons.shellui.dev) on every push to `main` (`docs.yml`).
**What's released:** only `ShellIcons.Blazor`. `ShellIcons.Maui` and `ShellIcons.Avalonia` are in preview — `release.yml` doesn't pack them yet. Publishing MAUI will need a Windows or macOS job with the MAUI workload; Avalonia packs on ubuntu, so it only needs adding to the existing pack step and the "already published" check. The docs site isn't part of a release either: it deploys to [shellicons.shellui.dev](https://shellicons.shellui.dev) on every push to `main` (`docs.yml`).

Publishing uses **NuGet Trusted Publishing** — GitHub Actions authenticates via OIDC and gets a short-lived (~1 hour) API key per run. No long-lived key lives in the repo.

Expand Down
3 changes: 3 additions & 0 deletions ShellIcons.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@
<Project Path="docs/ShellIcons.Docs/ShellIcons.Docs.csproj" />
</Folder>
<Folder Name="/src/">
<Project Path="src/ShellIcons.Avalonia/ShellIcons.Avalonia.csproj" />
<Project Path="src/ShellIcons.Blazor/ShellIcons.Blazor.csproj" />
<Project Path="src/ShellIcons.Generator.Xaml/ShellIcons.Generator.Xaml.csproj" />
<Project Path="src/ShellIcons.Generator/ShellIcons.Generator.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/ShellIcons.Avalonia.Tests/ShellIcons.Avalonia.Tests.csproj" />
<Project Path="tests/ShellIcons.Blazor.Tests/ShellIcons.Blazor.Tests.csproj" />
<Project Path="tests/ShellIcons.Generator.Tests/ShellIcons.Generator.Tests.csproj" />
</Folder>
Expand Down
6 changes: 3 additions & 3 deletions catalog/custom/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ Match Lucide's shape contract so custom icons are visually cohesive with the res
- **fill:** `none` on the root, unless a specific shape needs a fill (fill variants use `fill="currentColor"` on the elements that should be filled)
- **Inner children:** any of `<path>`, `<circle>`, `<ellipse>`, `<rect>`, `<line>`, `<polyline>`, `<polygon>`. The generator strips the outer `<svg>` and preserves the shapes.

## Staying portable to MAUI (and Avalonia)
## Staying portable to MAUI and Avalonia

Blazor renders your SVG as-is, so almost anything works there. The XAML targets convert every shape to native path geometry at build time, and they only understand the flat contract above. Break it and the icon still works in Blazor, but the build reports a problem for MAUI:
Blazor renders your SVG as-is, so almost anything works there. The XAML targets convert every shape to native path geometry at build time, and they only understand the flat contract above. Break it and the icon still works in Blazor, but the build reports a problem for MAUI and Avalonia:

| You use | What happens for MAUI | Diagnostic |
| You use | What happens on MAUI and Avalonia | Diagnostic |
|---|---|---|
| `<g>`, `<use>`, `<defs>`, gradients, masks, text | That element is skipped | `SHELLICONS002` (warning) |
| `transform="…"` on any shape | Reported; bake the transform into the coordinates instead | `SHELLICONS002` (warning) |
Expand Down
4 changes: 1 addition & 3 deletions docs/ShellIcons.Docs/Components/Preview/PreviewCell.razor
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
@namespace ShellIcons.Docs.Preview

@* Colored cell for icon showcases — sets `color` so nested icons pick it up
via currentColor. Alternative to <span style="color:…"> which can't be used
directly as a razor:preview root either. *@
@* Colored cell for icon showcases: sets `color` so nested icons pick it up via currentColor. *@
<span class="preview-cell" style="display:inline-flex;align-items:center;gap:@Gap;@(Color is null ? "" : $"color:{Color}")">
@ChildContent
</span>
Expand Down
4 changes: 1 addition & 3 deletions docs/ShellIcons.Docs/Components/Preview/PreviewRow.razor
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
@namespace ShellIcons.Docs.Preview

@* Simple flex-row wrapper for razor:preview blocks that showcase multiple icons.
ShellDocs's razor:preview fences require a registered component as the outer
tag — a bare <div> won't resolve — so this exists to be that outer component. *@
@* Flex-row wrapper for razor:preview blocks that showcase several icons. *@
<div class="preview-row" style="@_style">
@ChildContent
</div>
Expand Down
5 changes: 2 additions & 3 deletions docs/ShellIcons.Docs/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
{
o.ContentRoot = Path.Combine(builder.Environment.ContentRootPath, "content");
o.SiteName = "ShellIcons";
o.SiteTagline = "Lucide-derived icons for Blazor and .NET MAUI.";
o.SiteTagline = "Lucide-derived icons for Blazor, .NET MAUI and Avalonia.";
o.GitHubRepo = "shellui-dev/shell-icons";
o.LayoutVariant = DocsLayoutVariant.Sidebar;

Expand All @@ -23,8 +23,7 @@
// Every icon component, usable in razor:preview blocks.
o.RegisterComponentsFromAssembly<global::ShellIcons.IconCore>();

/* This site's own components: PreviewRow/PreviewCell (razor:preview needs a registered outer tag,
and Markdig mangles plain <span> wrappers) and IconBrowser (used from content/docs/icons.md). */
// This site's own components: PreviewRow/PreviewCell layout helpers and IconBrowser (content/docs/icons.md).
o.RegisterComponentsFromAssembly<ShellIcons.Docs.Preview.PreviewRow>();
});

Expand Down
8 changes: 4 additions & 4 deletions docs/ShellIcons.Docs/ShellIcons.Docs.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="ShellDocs.Tokens" Version="0.1.7-alpha" />
<PackageReference Include="ShellDocs.Components" Version="0.1.7-alpha" />
<PackageReference Include="ShellDocs.Core" Version="0.1.7-alpha" />
<PackageReference Include="ShellDocs.Markdown" Version="0.1.7-alpha" />
<PackageReference Include="ShellDocs.Tokens" Version="0.1.11-alpha" />
<PackageReference Include="ShellDocs.Components" Version="0.1.11-alpha" />
<PackageReference Include="ShellDocs.Core" Version="0.1.11-alpha" />
<PackageReference Include="ShellDocs.Markdown" Version="0.1.11-alpha" />
</ItemGroup>

<ItemGroup>
Expand Down
110 changes: 110 additions & 0 deletions docs/ShellIcons.Docs/content/docs/avalonia/getting-started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
---
title: Avalonia getting started
description: ShellIcons for Avalonia — the same 1,555 icons, drawn natively and colored like text.
order: 1
---

# ShellIcons for Avalonia

The same Lucide catalog as the Blazor and MAUI packages, drawn directly by Avalonia. The SVG is converted at build time, so nothing parses SVG in your app.

<Callout Variant="warning" Title="Preview — not on NuGet yet">
ShellIcons.Avalonia is covered by tests that render every icon through Avalonia's Skia backend, but it hasn't been run in a real desktop window yet. Reference the project from source until it's published.
</Callout>

## Install

Until the package is published, reference the project. No extra workloads are needed.

```xml
<ProjectReference Include="path/to/shell-icons/src/ShellIcons.Avalonia/ShellIcons.Avalonia.csproj" />
```

Targets `net8.0` and Avalonia 12.1.

## Use in XAML

One namespace covers everything:

```xml
<Window xmlns:icons="https://shellicons.dev/avalonia">
<StackPanel Orientation="Horizontal" Spacing="8">
<icons:ChevronRight Size="16" />
<icons:Icon Kind="Search" Foreground="Crimson" />
</StackPanel>
</Window>
```

There are two forms:

| Form | Example | Use for |
|---|---|---|
| Typed control | `<icons:ChevronRight />` | Icons fixed in your layout. Each carries its own shape, so unused icons are trimmed. |
| `Icon` dispatcher | `<icons:Icon Kind="Search" />` | Icons that are values — bindings and view-model properties. Keeps the whole catalog. |

The dispatcher's property is `Kind`, not `Name`: in Avalonia, `Name` is the element's own name.

XAML prefixes keep names apart, so `<icons:Image />` (the icon) and `<Image />` (Avalonia's control) coexist in the same file.

## Properties

| Property | Default | Notes |
|---|---|---|
| `Size` | `24` | Width and height |
| `StrokeThickness` | `2` | On Lucide's 24-unit grid; scales with `Size` |
| `AbsoluteStroke` | `false` | Keeps the rendered stroke at `StrokeThickness` at any size |
| `Foreground` | inherited | Stroke (and fill) brush |
| `Title` | `null` | Accessible name; when null the icon stays out of the accessibility tree |

## Color and theming

`Foreground` is the same inherited property that text uses. An icon takes the color of the text around it, like `currentColor` in the browser, and follows the theme with no extra work:

```xml
<Button Foreground="White" Background="RoyalBlue">
<StackPanel Orientation="Horizontal" Spacing="6">
<icons:Save Size="16" /> <!-- white, from the button -->
<TextBlock Text="Save" />
</StackPanel>
</Button>
```

Set it directly to override, or style every icon at once — `IconView` is the base of both the typed controls and `Icon`:

```xml
<Style Selector=":is(icons|IconView)">
<Setter Property="Foreground" Value="{DynamicResource SystemAccentColorBrush}" />
</Style>
```

## Clicks and accessibility

Icons are not hit-test visible: a click goes to the button or row that hosts the icon. To make an icon clickable, put it in a `Button`:

```xml
<Button Command="{Binding CloseCommand}">
<icons:X Title="Close" />
</Button>
```

Set `Title` whenever the icon is the only thing conveying meaning. It becomes the icon's automation name, and the icon is exposed to screen readers as an image.

## From C#

```csharp
using Avalonia.Media;
using ShellIcons.Avalonia;

var chevron = new ShellIcons.Avalonia.Icons.ChevronRight { Size = 16 };
var status = new Icon { Kind = IconName.CircleCheck, Foreground = Brushes.Green };
```

The typed controls live in `ShellIcons.Avalonia.Icons` so names like `Image` or `Grid` don't clash with Avalonia's own types in C#.

## How it draws

Each icon is a single custom-drawn control with no child elements. Its path data is the same build-time conversion the MAUI package uses (see [How rendering works](/docs/maui/rendering)); Avalonia turns it into a geometry once per icon and every control showing that icon shares it. Icons are drawn on Lucide's 24×24 grid and scaled to `Size`, so different icons of the same size line up.

## Next

- [Icon names & catalog](/docs/avalonia/names-and-catalog) — `IconName`, aliases, search, bindings
4 changes: 4 additions & 0 deletions docs/ShellIcons.Docs/content/docs/avalonia/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"title": "Avalonia (preview)",
"pages": ["getting-started", "names-and-catalog"]
}
Loading
Loading