diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c6cf51c..1d2850f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,15 +16,26 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: | - 8.0.x - 9.0.x + dotnet-version: 10.0.x - - name: Restore dependencies (src only) - run: dotnet restore src/ + - name: Restore dependencies (src + tests) + run: | + dotnet restore src/ShellUI.Native.CLI/ShellUI.Native.CLI.csproj + dotnet restore tests/ShellUI.Native.Tests/ShellUI.Native.Tests.csproj - name: Build library projects - run: dotnet build src/ --no-restore --configuration Release + run: dotnet build src/ShellUI.Native.CLI/ShellUI.Native.CLI.csproj --no-restore --configuration Release + + - name: Run tests + run: dotnet test tests/ShellUI.Native.Tests/ShellUI.Native.Tests.csproj --configuration Release --no-restore --logger "trx;LogFileName=test-results.trx" --collect:"XPlat Code Coverage" + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results + path: | + tests/**/TestResults/** - name: Pack CLI tool run: dotnet pack src/ShellUI.Native.CLI/ShellUI.Native.CLI.csproj --no-restore --configuration Release -o ./nupkg @@ -45,8 +56,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: | - 8.0.x + dotnet-version: 10.0.x - name: Install MAUI workload run: dotnet workload install maui @@ -55,4 +65,4 @@ jobs: run: dotnet restore examples/MAUI.Demo/MAUI.Demo.csproj - name: Build MAUI Demo (Windows) - run: dotnet build examples/MAUI.Demo/MAUI.Demo.csproj --configuration Release --framework net8.0-windows10.0.19041.0 + run: dotnet build examples/MAUI.Demo/MAUI.Demo.csproj --configuration Release --framework net10.0-windows10.0.19041.0 diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 5323545..d07e7b5 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -15,7 +15,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: 8.0.x + dotnet-version: 10.0.x - name: Check formatting run: dotnet format src/ --verify-no-changes --verbosity diagnostic diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 889274e..00fec8d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,9 +16,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: | - 8.0.x - 9.0.x + dotnet-version: 10.0.x - name: Extract version from tag id: version diff --git a/Directory.Build.props b/Directory.Build.props index ae737ef..a5a6e8c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -23,15 +23,13 @@ © $([System.DateTime]::Now.Year) ShellUI. All rights reserved. MIT false - maui;winui;wpf;ui;components;shadcn;native;cross-platform + maui;avalonia;winui;ui;components;shadcn;native;cross-platform https://github.com/shellui-dev/shellui-native git - + - - net8.0 enable enable latest diff --git a/ShellUI.Native.sln b/ShellUI.Native.sln deleted file mode 100644 index 23a1c10..0000000 --- a/ShellUI.Native.sln +++ /dev/null @@ -1,60 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.8.34525.116 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShellUI.Native.Core", "src\ShellUI.Native.Core\ShellUI.Native.Core.csproj", "{A1B2C3D4-E5F6-1234-5678-9ABCDEF01234}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShellUI.Native.Templates", "src\ShellUI.Native.Templates\ShellUI.Native.Templates.csproj", "{B2C3D4E5-F6A1-2345-6789-ABCDEF012345}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShellUI.Native.CLI", "src\ShellUI.Native.CLI\ShellUI.Native.CLI.csproj", "{C3D4E5F6-A1B2-3456-789A-BCDEF0123456}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MAUI.Demo", "examples\MAUI.Demo\MAUI.Demo.csproj", "{D5E6F7A8-B9C0-1234-5678-DEF012345678}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D4E5F6A1-B2C3-4567-89AB-CDEF01234567}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{E5F6A1B2-C3D4-5678-9ABC-DEF012345678}" - ProjectSection(SolutionItems) = preProject - docs\README.md = docs\README.md - docs\ARCHITECTURE.md = docs\ARCHITECTURE.md - docs\PLAN.md = docs\PLAN.md - docs\BLAZOR_HYBRID.md = docs\BLAZOR_HYBRID.md - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{F6A7B8C9-D0E1-2345-6789-ABC012345678}" - ProjectSection(SolutionItems) = preProject - examples\README.md = examples\README.md - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A1B2C3D4-E5F6-1234-5678-9ABCDEF01234}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A1B2C3D4-E5F6-1234-5678-9ABCDEF01234}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A1B2C3D4-E5F6-1234-5678-9ABCDEF01234}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A1B2C3D4-E5F6-1234-5678-9ABCDEF01234}.Release|Any CPU.Build.0 = Release|Any CPU - {B2C3D4E5-F6A1-2345-6789-ABCDEF012345}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B2C3D4E5-F6A1-2345-6789-ABCDEF012345}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B2C3D4E5-F6A1-2345-6789-ABCDEF012345}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B2C3D4E5-F6A1-2345-6789-ABCDEF012345}.Release|Any CPU.Build.0 = Release|Any CPU - {C3D4E5F6-A1B2-3456-789A-BCDEF0123456}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C3D4E5F6-A1B2-3456-789A-BCDEF0123456}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C3D4E5F6-A1B2-3456-789A-BCDEF0123456}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C3D4E5F6-A1B2-3456-789A-BCDEF0123456}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {A1B2C3D4-E5F6-1234-5678-9ABCDEF01234} = {D4E5F6A1-B2C3-4567-89AB-CDEF01234567} - {B2C3D4E5-F6A1-2345-6789-ABCDEF012345} = {D4E5F6A1-B2C3-4567-89AB-CDEF01234567} - {C3D4E5F6-A1B2-3456-789A-BCDEF0123456} = {D4E5F6A1-B2C3-4567-89AB-CDEF01234567} - {D5E6F7A8-B9C0-1234-5678-DEF012345678} = {F6A7B8C9-D0E1-2345-6789-ABC012345678} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {F6A1B2C3-D4E5-6789-ABCD-EF0123456789} - EndGlobalSection -EndGlobal diff --git a/ShellUI.Native.slnx b/ShellUI.Native.slnx new file mode 100644 index 0000000..0ecb332 --- /dev/null +++ b/ShellUI.Native.slnx @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index c3c2bb4..2a86a6c 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -2,13 +2,13 @@ ## Overview -ShellUI Native follows a modular architecture designed to support multiple native platforms while maintaining a consistent developer experience and design system. +ShellUI Native follows a modular architecture designed to support multiple native platforms while maintaining a consistent developer experience and design system. It is the native counterpart to [ShellUI (Blazor)](https://shellui.dev/), inspired by [shadcn/ui](https://ui.shadcn.com/)'s copy-and-own approach. ## Key Difference from ShellUI Blazor: No Tailwind! **ShellUI Native does NOT use Tailwind CSS.** Native platforms use XAML styles, not CSS. -| Aspect | ShellUI Blazor | ShellUI Native | +| Aspect | [ShellUI Blazor](https://shellui.dev/) | ShellUI Native | |--------|---------------|----------------| | Styling | Tailwind CSS | XAML Styles/ResourceDictionary | | Rendering | HTML/CSS in browser | Native platform controls | @@ -21,14 +21,18 @@ The design tokens (colors, spacing, typography) are **identical** to maintain vi ``` shellui-native/ -├── Directory.Build.props # Centralized versioning -├── ShellUI.Native.sln +├── Directory.Build.props # Centralized versioning + package metadata +├── global.json # Pins .NET 10 SDK +├── ShellUI.Native.slnx # XML-format solution (post .NET 9) ├── src/ │ ├── ShellUI.Native.Core/ # Shared models and abstractions -│ ├── ShellUI.Native.Templates/ # Component templates +│ ├── ShellUI.Native.Templates/ # Component templates (MAUI today; per-platform planned) │ ├── ShellUI.Native.CLI/ # Command-line tool -│ └── ShellUI.Native.MAUI/ # Reference implementation (optional) -├── samples/ +│ ├── ShellUI.Native.MAUI/ # MAUI reference implementation (optional) +│ └── ShellUI.Native.Avalonia/ # Avalonia reference implementation (planned, Phase 2) +├── tests/ +│ └── ShellUI.Native.Tests/ # xUnit — registry invariants, template hygiene, ProjectDetector +├── examples/ │ └── MAUI.Demo/ └── docs/ ``` @@ -69,6 +73,13 @@ public class ButtonTemplate } ``` +**Gap today (Template System v2 target):** `Content` is a single MAUI-flavored string, and +`ComponentRegistry.GetComponentContent` has no platform parameter — it can't yet return +different code for Avalonia vs. MAUI. Adding Avalonia support requires evolving this to +something like `ButtonTemplate.Content(NativePlatform)` or per-platform static properties +(`ButtonTemplate.MauiContent` / `ButtonTemplate.AvaloniaContent`) selected by +`config.TargetPlatform`. See [PLAN.md](./PLAN.md) and [DEVELOPMENT_PLAN.md](./DEVELOPMENT_PLAN.md). + ### 2. Namespace Replacement When components are installed, the placeholder `YourProjectNamespace` is replaced with the actual project namespace detected from the .csproj file. @@ -81,7 +92,8 @@ Components can declare dependencies on other components. The CLI automatically i The `shellui-native.json` file tracks: -- Target platform (MAUI, WinUI, WPF) +- Target platform (MAUI, Avalonia, WinUI, or WPF — see `NativePlatform` in + [ShellUINativeConfig.cs](../src/ShellUI.Native.Core/Models/ShellUINativeConfig.cs)) - Components path - Installed components with versions - Theme settings @@ -93,16 +105,29 @@ The CLI detects project types by examining the .csproj file: | Detection | Platform | |-----------|----------| | `UseMaui=true` or SDK contains "Maui" | MAUI | +| `PackageReference` starting with "Avalonia", or an `App.axaml` file present | Avalonia | | `UseWinUI=true` | WinUI 3 | -| `UseWPF=true` or SDK contains "Wpf" | WPF | +| `UseWPF=true` or SDK contains "Wpf" | WPF (detection only — see [PLAN.md](./PLAN.md)) | + +Detection order matters: MAUI is checked first (via SDK/`UseMaui`), then Avalonia (via +`PackageReference`/`App.axaml`, since Avalonia has no dedicated SDK), then WinUI/WPF. See +[`ProjectDetector.cs`](../src/ShellUI.Native.CLI/Services/ProjectDetector.cs). + +**Current limitation:** detection recognizes Avalonia/WinUI/WPF projects, but +[`ComponentRegistry`](../src/ShellUI.Native.Templates/ComponentRegistry.cs) only has MAUI +templates today. `shellui-native add` prints a warning and installs MAUI code regardless of +detected platform until per-platform templates exist (see "Component Templates" in Core +Concepts above, and Template System v2 in [PLAN.md](./PLAN.md)). ## Theming System -Unlike ShellUI Blazor which uses CSS variables, ShellUI Native uses platform-native theming: +Unlike [ShellUI Blazor](https://shellui.dev/) which uses CSS variables, ShellUI Native uses platform-native theming: - **MAUI** - ResourceDictionary with Colors, Styles +- **Avalonia** - `Styles`/`ResourceDictionary` with `DynamicResource`, plus Avalonia's built-in + Fluent/Simple theme variants for light/dark switching - **WinUI** - XAML Resources and ThemeResources -- **WPF** - ResourceDictionary with DynamicResource +- **WPF** - ResourceDictionary with DynamicResource (existing apps only, not an active target) Design tokens are mapped from the ShellUI CSS variables to native equivalents: @@ -138,12 +163,40 @@ public partial class Button : ContentView } ``` +## Component Pattern (Avalonia, planned) + +Avalonia components will follow the `StyledProperty`/`TemplatedControl` pattern instead of +MAUI's `BindableProperty`/`ContentView` — the same property-driven visual-state approach, just +Avalonia's equivalent APIs: + +```csharp +public partial class Button : TemplatedControl +{ + public static readonly StyledProperty VariantProperty = + AvaloniaProperty.Register(nameof(Variant), ButtonVariant.Default); + + public ButtonVariant Variant + { + get => GetValue(VariantProperty); + set => SetValue(VariantProperty, value); + } + + static Button() + { + VariantProperty.Changed.AddClassHandler