From c4418b896dd969ea4f67c0e0d3e7b6224a69b8f9 Mon Sep 17 00:00:00 2001 From: Shephard Tseisi Date: Sat, 29 Aug 2026 21:10:52 +0200 Subject: [PATCH 1/8] refactor: enhance platform support checks in ComponentInstaller, ComponentManager, and InitService to skip components without templates for the target platform --- .../Services/ComponentInstaller.cs | 18 +++++++++++------- .../Services/ComponentManager.cs | 9 ++++++++- src/ShellUI.Native.CLI/Services/InitService.cs | 10 +++++++++- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/ShellUI.Native.CLI/Services/ComponentInstaller.cs b/src/ShellUI.Native.CLI/Services/ComponentInstaller.cs index c998458..73b8550 100644 --- a/src/ShellUI.Native.CLI/Services/ComponentInstaller.cs +++ b/src/ShellUI.Native.CLI/Services/ComponentInstaller.cs @@ -30,12 +30,6 @@ public static async Task InstallComponents(string[] components, bool force) var projectInfo = ProjectDetector.DetectProject(); - if (config.TargetPlatform != NativePlatform.MAUI) - { - AnsiConsole.MarkupLine($"[yellow]Warning:[/] component templates are currently MAUI-only. " + - $"'{config.TargetPlatform}' support is planned but not yet implemented — installed files will contain MAUI code."); - } - // Parse comma-separated components var componentList = new List(); foreach (var comp in components) @@ -163,7 +157,17 @@ private static async Task InstallComponentInternalAsync( return InstallResult.Skipped; } - var content = ComponentRegistry.GetComponentContent(componentName); + if (!ComponentRegistry.SupportsPlatform(componentName, config.TargetPlatform)) + { + var supported = ComponentRegistry.GetSupportedPlatforms(componentName); + var supportedList = supported.Count == 0 ? "(none)" : string.Join(", ", supported); + AnsiConsole.MarkupLine( + $"[red]Component '{componentName}' has no template for {config.TargetPlatform}.[/] " + + $"[dim]Supported platforms: {supportedList}. See docs/DEVELOPMENT_PLAN.md for the roadmap.[/]"); + return InstallResult.Failed; + } + + var content = ComponentRegistry.GetComponentContent(componentName, config.TargetPlatform); if (content == null) { AnsiConsole.MarkupLine($"[red]Failed to get content for '{componentName}'[/]"); diff --git a/src/ShellUI.Native.CLI/Services/ComponentManager.cs b/src/ShellUI.Native.CLI/Services/ComponentManager.cs index 7ac86eb..f1f2d92 100644 --- a/src/ShellUI.Native.CLI/Services/ComponentManager.cs +++ b/src/ShellUI.Native.CLI/Services/ComponentManager.cs @@ -171,7 +171,14 @@ public static void UpdateComponents(string[] components, bool all) continue; } - var content = ComponentRegistry.GetComponentContent(componentName); + if (!ComponentRegistry.SupportsPlatform(componentName, config.TargetPlatform)) + { + AnsiConsole.MarkupLine( + $"[yellow]Skipped '{componentName}': no template for {config.TargetPlatform}.[/]"); + continue; + } + + var content = ComponentRegistry.GetComponentContent(componentName, config.TargetPlatform); if (content == null) continue; content = content.Replace("YourProjectNamespace", projectInfo.RootNamespace); diff --git a/src/ShellUI.Native.CLI/Services/InitService.cs b/src/ShellUI.Native.CLI/Services/InitService.cs index aa2d265..ab0bcc3 100644 --- a/src/ShellUI.Native.CLI/Services/InitService.cs +++ b/src/ShellUI.Native.CLI/Services/InitService.cs @@ -130,7 +130,15 @@ await AnsiConsole.Status() private static async Task InstallShellUtilityAsync(ProjectInfo projectInfo, string componentsPath) { - var content = ComponentRegistry.GetComponentContent("shell"); + if (!ComponentRegistry.SupportsPlatform("shell", projectInfo.Platform)) + { + AnsiConsole.MarkupLine( + $"[yellow]Shell utility has no template for {projectInfo.Platform} yet — skipping.[/] " + + $"[dim]Add components once per-platform templates land.[/]"); + return; + } + + var content = ComponentRegistry.GetComponentContent("shell", projectInfo.Platform); if (content == null) return; content = content.Replace("YourProjectNamespace", projectInfo.RootNamespace); From 41a9f61c1a582875e5d2f63da7edfd340fc6b72b Mon Sep 17 00:00:00 2001 From: Shephard Tseisi Date: Sat, 29 Aug 2026 21:11:25 +0200 Subject: [PATCH 2/8] refactor: update UI component templates to use IReadOnlyDictionary for platform-specific content and enhance structure for better maintainability --- .../Templates/AlertTemplate.cs | 7 +++++-- .../Templates/BadgeTemplate.cs | 7 +++++-- .../Templates/ButtonTemplate.cs | 7 +++++-- .../Templates/ButtonVariantsTemplate.cs | 7 +++++-- .../Templates/CardContentTemplate.cs | 7 +++++-- .../Templates/CardFooterTemplate.cs | 7 +++++-- .../Templates/CardHeaderTemplate.cs | 7 +++++-- .../Templates/CardTemplate.cs | 7 +++++-- .../Templates/CheckboxTemplate.cs | 7 +++++-- .../Templates/DatePickerTemplate.cs | 7 +++++-- .../Templates/DialogCloseTemplate.cs | 7 +++++-- .../Templates/DialogContentTemplate.cs | 7 +++++-- .../Templates/DialogDescriptionTemplate.cs | 7 +++++-- .../Templates/DialogFooterTemplate.cs | 7 +++++-- .../Templates/DialogHeaderTemplate.cs | 7 +++++-- .../Templates/DialogTemplate.cs | 7 +++++-- .../Templates/DialogTitleTemplate.cs | 7 +++++-- .../Templates/DialogTriggerTemplate.cs | 7 +++++-- .../Templates/DrawerContentTemplate.cs | 7 +++++-- .../Templates/DrawerTemplate.cs | 7 +++++-- .../Templates/DrawerTriggerTemplate.cs | 7 +++++-- .../Templates/DropdownContentTemplate.cs | 7 +++++-- .../Templates/DropdownItemTemplate.cs | 10 +++++++--- .../Templates/DropdownTemplate.cs | 7 +++++-- .../Templates/DropdownTriggerTemplate.cs | 7 +++++-- .../Templates/ElementExtensionsTemplate.cs | 7 +++++-- .../Templates/InputTemplate.cs | 12 ++++++++---- .../Templates/LabelTemplate.cs | 7 +++++-- .../Templates/PopoverContentTemplate.cs | 7 +++++-- .../Templates/PopoverTemplate.cs | 7 +++++-- .../Templates/PopoverTriggerTemplate.cs | 7 +++++-- .../Templates/ProgressTemplate.cs | 7 +++++-- .../Templates/RadioGroupItemTemplate.cs | 11 +++++++---- .../Templates/RadioGroupTemplate.cs | 7 +++++-- .../Templates/SelectTemplate.cs | 7 +++++-- .../Templates/SeparatorTemplate.cs | 7 +++++-- .../Templates/SheetContentTemplate.cs | 7 +++++-- .../Templates/SheetTemplate.cs | 7 +++++-- .../Templates/SheetTriggerTemplate.cs | 7 +++++-- .../Templates/ShellTemplate.cs | 7 +++++-- .../Templates/SliderTemplate.cs | 7 +++++-- .../Templates/SwitchTemplate.cs | 7 +++++-- .../Templates/TextareaTemplate.cs | 7 +++++-- .../Templates/TimePickerTemplate.cs | 7 +++++-- 44 files changed, 227 insertions(+), 93 deletions(-) diff --git a/src/ShellUI.Native.Templates/Templates/AlertTemplate.cs b/src/ShellUI.Native.Templates/Templates/AlertTemplate.cs index b8ad14f..a948cf6 100644 --- a/src/ShellUI.Native.Templates/Templates/AlertTemplate.cs +++ b/src/ShellUI.Native.Templates/Templates/AlertTemplate.cs @@ -17,7 +17,9 @@ public static class AlertTemplate Tags = new List { "feedback", "alert", "notification", "message", "banner" } }; - public static string Content => @"using Microsoft.Maui.Controls.Shapes; + public static IReadOnlyDictionary Contents { get; } = new Dictionary + { + [NativePlatform.MAUI] = @"using Microsoft.Maui.Controls.Shapes; namespace YourProjectNamespace.Components.UI; // Alert component for notifications and feedback @@ -175,6 +177,7 @@ public enum AlertVariant Warning, Info } -"; +" + }; } \ No newline at end of file diff --git a/src/ShellUI.Native.Templates/Templates/BadgeTemplate.cs b/src/ShellUI.Native.Templates/Templates/BadgeTemplate.cs index 92c637a..abee8ad 100644 --- a/src/ShellUI.Native.Templates/Templates/BadgeTemplate.cs +++ b/src/ShellUI.Native.Templates/Templates/BadgeTemplate.cs @@ -17,7 +17,9 @@ public static class BadgeTemplate Tags = new List { "status", "indicator", "badge", "tag", "chip" } }; - public static string Content => @"using Microsoft.Maui.Controls.Shapes; + public static IReadOnlyDictionary Contents { get; } = new Dictionary + { + [NativePlatform.MAUI] = @"using Microsoft.Maui.Controls.Shapes; namespace YourProjectNamespace.Components.UI; // Badge status indicator component @@ -130,5 +132,6 @@ private void UpdateVisualState() } public enum BadgeVariant { Default, Secondary, Destructive, Outline, Success, Warning } -"; +" + }; } diff --git a/src/ShellUI.Native.Templates/Templates/ButtonTemplate.cs b/src/ShellUI.Native.Templates/Templates/ButtonTemplate.cs index c54ec43..435c7dc 100644 --- a/src/ShellUI.Native.Templates/Templates/ButtonTemplate.cs +++ b/src/ShellUI.Native.Templates/Templates/ButtonTemplate.cs @@ -17,7 +17,9 @@ public static class ButtonTemplate Tags = new List { "form", "input", "interactive", "button", "action" } }; - public static string Content => @"using Microsoft.Maui.Controls.Shapes; + public static IReadOnlyDictionary Contents { get; } = new Dictionary + { + [NativePlatform.MAUI] = @"using Microsoft.Maui.Controls.Shapes; using YourProjectNamespace.Components.UI.Variants; namespace YourProjectNamespace.Components.UI; @@ -153,5 +155,6 @@ private void UpdateVisualState() Opacity = IsEnabled ? 1.0 : 0.5; } } -"; +" + }; } diff --git a/src/ShellUI.Native.Templates/Templates/ButtonVariantsTemplate.cs b/src/ShellUI.Native.Templates/Templates/ButtonVariantsTemplate.cs index 7e0e8c9..6483168 100644 --- a/src/ShellUI.Native.Templates/Templates/ButtonVariantsTemplate.cs +++ b/src/ShellUI.Native.Templates/Templates/ButtonVariantsTemplate.cs @@ -15,7 +15,9 @@ public static class ButtonVariantsTemplate IsAvailable = false // Installed as dependency of button }; - public static string Content => @"namespace YourProjectNamespace.Components.UI.Variants; + public static IReadOnlyDictionary Contents { get; } = new Dictionary + { + [NativePlatform.MAUI] = @"namespace YourProjectNamespace.Components.UI.Variants; public enum ButtonVariant { @@ -124,5 +126,6 @@ public static ButtonStyle GetStyle(ButtonVariant variant = ButtonVariant.Default return style; } } -"; +" + }; } diff --git a/src/ShellUI.Native.Templates/Templates/CardContentTemplate.cs b/src/ShellUI.Native.Templates/Templates/CardContentTemplate.cs index 4584c83..b73cbd7 100644 --- a/src/ShellUI.Native.Templates/Templates/CardContentTemplate.cs +++ b/src/ShellUI.Native.Templates/Templates/CardContentTemplate.cs @@ -17,7 +17,9 @@ public static class CardContentTemplate Tags = new List { "layout", "card", "content" } }; - public static string Content => @"namespace YourProjectNamespace.Components.UI; + public static IReadOnlyDictionary Contents { get; } = new Dictionary + { + [NativePlatform.MAUI] = @"namespace YourProjectNamespace.Components.UI; // Card main content section public partial class CardContent : ContentView @@ -68,5 +70,6 @@ private static void OnNoPaddingChanged(BindableObject bindable, object oldValue, } } } -"; +" + }; } diff --git a/src/ShellUI.Native.Templates/Templates/CardFooterTemplate.cs b/src/ShellUI.Native.Templates/Templates/CardFooterTemplate.cs index 6d7689c..2fd0b8f 100644 --- a/src/ShellUI.Native.Templates/Templates/CardFooterTemplate.cs +++ b/src/ShellUI.Native.Templates/Templates/CardFooterTemplate.cs @@ -17,7 +17,9 @@ public static class CardFooterTemplate Tags = new List { "layout", "card", "footer", "actions" } }; - public static string Content => @"namespace YourProjectNamespace.Components.UI; + public static IReadOnlyDictionary Contents { get; } = new Dictionary + { + [NativePlatform.MAUI] = @"namespace YourProjectNamespace.Components.UI; // Card footer section for actions public partial class CardFooter : ContentView @@ -103,5 +105,6 @@ private static void OnJustifyChanged(BindableObject bindable, object oldValue, o } public enum FooterJustify { Start, Center, End, SpaceBetween } -"; +" + }; } diff --git a/src/ShellUI.Native.Templates/Templates/CardHeaderTemplate.cs b/src/ShellUI.Native.Templates/Templates/CardHeaderTemplate.cs index 1ed523d..b1cd1c4 100644 --- a/src/ShellUI.Native.Templates/Templates/CardHeaderTemplate.cs +++ b/src/ShellUI.Native.Templates/Templates/CardHeaderTemplate.cs @@ -17,7 +17,9 @@ public static class CardHeaderTemplate Tags = new List { "layout", "card", "header" } }; - public static string Content => @"namespace YourProjectNamespace.Components.UI; + public static IReadOnlyDictionary Contents { get; } = new Dictionary + { + [NativePlatform.MAUI] = @"namespace YourProjectNamespace.Components.UI; // Card header section public partial class CardHeader : ContentView @@ -88,5 +90,6 @@ private static void OnDescriptionChanged(BindableObject bindable, object oldValu } } } -"; +" + }; } diff --git a/src/ShellUI.Native.Templates/Templates/CardTemplate.cs b/src/ShellUI.Native.Templates/Templates/CardTemplate.cs index 08405ca..1c046ec 100644 --- a/src/ShellUI.Native.Templates/Templates/CardTemplate.cs +++ b/src/ShellUI.Native.Templates/Templates/CardTemplate.cs @@ -17,7 +17,9 @@ public static class CardTemplate Tags = new List { "layout", "container", "card", "panel", "surface" } }; - public static string Content => @"using Microsoft.Maui.Controls.Shapes; + public static IReadOnlyDictionary Contents { get; } = new Dictionary + { + [NativePlatform.MAUI] = @"using Microsoft.Maui.Controls.Shapes; namespace YourProjectNamespace.Components.UI; // Card container - compositional children via Dependencies (CardHeader, CardContent, CardFooter) @@ -108,5 +110,6 @@ private void UpdateVisualState() } public enum CardVariant { Default, Bordered, Elevated } -"; +" + }; } diff --git a/src/ShellUI.Native.Templates/Templates/CheckboxTemplate.cs b/src/ShellUI.Native.Templates/Templates/CheckboxTemplate.cs index 19e3fc5..cc4e41d 100644 --- a/src/ShellUI.Native.Templates/Templates/CheckboxTemplate.cs +++ b/src/ShellUI.Native.Templates/Templates/CheckboxTemplate.cs @@ -17,7 +17,9 @@ public static class CheckboxTemplate Tags = new List { "form", "checkbox", "input", "selection" } }; - public static string Content => @"using Microsoft.Maui.Controls.Shapes; + public static IReadOnlyDictionary Contents { get; } = new Dictionary + { + [NativePlatform.MAUI] = @"using Microsoft.Maui.Controls.Shapes; namespace YourProjectNamespace.Components.UI; // Checkbox component with label support @@ -165,6 +167,7 @@ private void UpdateVisualState() _checkboxBorder.Opacity = IsEnabled ? 1.0 : 0.5; } } -"; +" + }; } \ No newline at end of file diff --git a/src/ShellUI.Native.Templates/Templates/DatePickerTemplate.cs b/src/ShellUI.Native.Templates/Templates/DatePickerTemplate.cs index a938859..6955c94 100644 --- a/src/ShellUI.Native.Templates/Templates/DatePickerTemplate.cs +++ b/src/ShellUI.Native.Templates/Templates/DatePickerTemplate.cs @@ -15,7 +15,9 @@ public static class DatePickerTemplate Tags = new List { "form", "date", "picker" } }; - public static string Content => @"using Microsoft.Maui.Controls.Shapes; + public static IReadOnlyDictionary Contents { get; } = new Dictionary + { + [NativePlatform.MAUI] = @"using Microsoft.Maui.Controls.Shapes; namespace YourProjectNamespace.Components.UI; public partial class DatePicker : ContentView @@ -86,5 +88,6 @@ private static void OnDateChanged(BindableObject b, object o, object n) dp._nativePicker.Date = dt; } } -"; +" + }; } diff --git a/src/ShellUI.Native.Templates/Templates/DialogCloseTemplate.cs b/src/ShellUI.Native.Templates/Templates/DialogCloseTemplate.cs index 8f8817d..157a755 100644 --- a/src/ShellUI.Native.Templates/Templates/DialogCloseTemplate.cs +++ b/src/ShellUI.Native.Templates/Templates/DialogCloseTemplate.cs @@ -15,7 +15,9 @@ public static class DialogCloseTemplate Tags = new List { "overlay", "dialog", "close" } }; - public static string Content => @"namespace YourProjectNamespace.Components.UI; + public static IReadOnlyDictionary Contents { get; } = new Dictionary + { + [NativePlatform.MAUI] = @"namespace YourProjectNamespace.Components.UI; public partial class DialogClose : ContentView { @@ -26,5 +28,6 @@ public DialogClose() GestureRecognizers.Add(tap); } } -"; +" + }; } diff --git a/src/ShellUI.Native.Templates/Templates/DialogContentTemplate.cs b/src/ShellUI.Native.Templates/Templates/DialogContentTemplate.cs index 84352b0..f32390e 100644 --- a/src/ShellUI.Native.Templates/Templates/DialogContentTemplate.cs +++ b/src/ShellUI.Native.Templates/Templates/DialogContentTemplate.cs @@ -15,7 +15,9 @@ public static class DialogContentTemplate Tags = new List { "overlay", "dialog", "content" } }; - public static string Content => @"using Microsoft.Maui.Controls.Shapes; + public static IReadOnlyDictionary Contents { get; } = new Dictionary + { + [NativePlatform.MAUI] = @"using Microsoft.Maui.Controls.Shapes; namespace YourProjectNamespace.Components.UI; // Modal content overlay - backdrop + centered box. Usage: ... @@ -66,5 +68,6 @@ public DialogContent() } } -"; +" + }; } diff --git a/src/ShellUI.Native.Templates/Templates/DialogDescriptionTemplate.cs b/src/ShellUI.Native.Templates/Templates/DialogDescriptionTemplate.cs index a2c03c0..47f5154 100644 --- a/src/ShellUI.Native.Templates/Templates/DialogDescriptionTemplate.cs +++ b/src/ShellUI.Native.Templates/Templates/DialogDescriptionTemplate.cs @@ -15,7 +15,9 @@ public static class DialogDescriptionTemplate Tags = new List { "overlay", "dialog", "description" } }; - public static string Content => @"namespace YourProjectNamespace.Components.UI; + public static IReadOnlyDictionary Contents { get; } = new Dictionary + { + [NativePlatform.MAUI] = @"namespace YourProjectNamespace.Components.UI; public partial class DialogDescription : Label { @@ -25,5 +27,6 @@ public DialogDescription() TextColor = Color.FromArgb(""#6B7280""); } } -"; +" + }; } diff --git a/src/ShellUI.Native.Templates/Templates/DialogFooterTemplate.cs b/src/ShellUI.Native.Templates/Templates/DialogFooterTemplate.cs index fa7e106..099a654 100644 --- a/src/ShellUI.Native.Templates/Templates/DialogFooterTemplate.cs +++ b/src/ShellUI.Native.Templates/Templates/DialogFooterTemplate.cs @@ -15,7 +15,9 @@ public static class DialogFooterTemplate Tags = new List { "overlay", "dialog", "footer" } }; - public static string Content => @"namespace YourProjectNamespace.Components.UI; + public static IReadOnlyDictionary Contents { get; } = new Dictionary + { + [NativePlatform.MAUI] = @"namespace YourProjectNamespace.Components.UI; [ContentProperty(nameof(Children))] public partial class DialogFooter : ContentView @@ -34,5 +36,6 @@ public DialogFooter() Content = _stack; } } -"; +" + }; } diff --git a/src/ShellUI.Native.Templates/Templates/DialogHeaderTemplate.cs b/src/ShellUI.Native.Templates/Templates/DialogHeaderTemplate.cs index 98a1286..ed349dd 100644 --- a/src/ShellUI.Native.Templates/Templates/DialogHeaderTemplate.cs +++ b/src/ShellUI.Native.Templates/Templates/DialogHeaderTemplate.cs @@ -15,7 +15,9 @@ public static class DialogHeaderTemplate Tags = new List { "overlay", "dialog", "header" } }; - public static string Content => @"namespace YourProjectNamespace.Components.UI; + public static IReadOnlyDictionary Contents { get; } = new Dictionary + { + [NativePlatform.MAUI] = @"namespace YourProjectNamespace.Components.UI; [ContentProperty(nameof(Children))] public partial class DialogHeader : ContentView @@ -30,5 +32,6 @@ public DialogHeader() Content = _stack; } } -"; +" + }; } diff --git a/src/ShellUI.Native.Templates/Templates/DialogTemplate.cs b/src/ShellUI.Native.Templates/Templates/DialogTemplate.cs index a71d642..5ab34f0 100644 --- a/src/ShellUI.Native.Templates/Templates/DialogTemplate.cs +++ b/src/ShellUI.Native.Templates/Templates/DialogTemplate.cs @@ -15,7 +15,9 @@ public static class DialogTemplate Tags = new List { "overlay", "modal", "dialog" } }; - public static string Content => @"namespace YourProjectNamespace.Components.UI; + public static IReadOnlyDictionary Contents { get; } = new Dictionary + { + [NativePlatform.MAUI] = @"namespace YourProjectNamespace.Components.UI; // Modal dialog - compositional. Usage: ...... public partial class Dialog : AbsoluteLayout @@ -75,5 +77,6 @@ private void OnOpenChanged() OpenChanged?.Invoke(this, Open); } } -"; +" + }; } diff --git a/src/ShellUI.Native.Templates/Templates/DialogTitleTemplate.cs b/src/ShellUI.Native.Templates/Templates/DialogTitleTemplate.cs index 6ff0eaf..a56d681 100644 --- a/src/ShellUI.Native.Templates/Templates/DialogTitleTemplate.cs +++ b/src/ShellUI.Native.Templates/Templates/DialogTitleTemplate.cs @@ -15,7 +15,9 @@ public static class DialogTitleTemplate Tags = new List { "overlay", "dialog", "title" } }; - public static string Content => @"namespace YourProjectNamespace.Components.UI; + public static IReadOnlyDictionary Contents { get; } = new Dictionary + { + [NativePlatform.MAUI] = @"namespace YourProjectNamespace.Components.UI; public partial class DialogTitle : Label { @@ -25,5 +27,6 @@ public DialogTitle() FontAttributes = FontAttributes.Bold; } } -"; +" + }; } diff --git a/src/ShellUI.Native.Templates/Templates/DialogTriggerTemplate.cs b/src/ShellUI.Native.Templates/Templates/DialogTriggerTemplate.cs index b70ca10..b4fad90 100644 --- a/src/ShellUI.Native.Templates/Templates/DialogTriggerTemplate.cs +++ b/src/ShellUI.Native.Templates/Templates/DialogTriggerTemplate.cs @@ -15,7 +15,9 @@ public static class DialogTriggerTemplate Tags = new List { "overlay", "dialog", "trigger" } }; - public static string Content => @"namespace YourProjectNamespace.Components.UI; + public static IReadOnlyDictionary Contents { get; } = new Dictionary + { + [NativePlatform.MAUI] = @"namespace YourProjectNamespace.Components.UI; // Tap to open dialog. Usage: